<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <title>CoffeeScript Test Suite</title>
  <script nomodule src="browser-compiler-legacy/coffeescript.js"></script>
  <script type="module">
    import CoffeeScript from './browser-compiler-modern/coffeescript.js';
    window.CoffeeScript = CoffeeScript;
    window.addEventListener('DOMContentLoaded', CoffeeScript.runScripts, false);
  </script>
  <script src="https://cdn.jsdelivr.net/underscorejs/1.8.3/underscore-min.js"></script>
  <style>
    body, pre {
      font-family: Consolas, Menlo, Monaco, monospace;
    }
    body {
      margin: 1em;
    }
    h1 {
      font-size: 1.3em;
    }
    div {
      margin: 0.6em;
    }
    .good {
      color: #22b24c
    }
    .bad {
      color: #eb6864
    }
    .subtle {
      font-size: 0.7em;
      color: #999999
    }
  </style>
</head>
<body>

<h1>CoffeeScript Test Suite</h1>

<pre id="stdout"></pre>

<script type="text/coffeescript">
@testingBrowser = yes
@global = window
@bold = @red = @green = @reset = ''
stdout = document.getElementById 'stdout'
start = new Date
@currentFile = ''
@passedTests = failedTests = total = done = 0

say = (msg, className, id) ->
  div = document.createElement 'div'
  div.className = className if className?
  div.id = id if id?
  div.appendChild document.createTextNode msg
  stdout.appendChild div
  msg

asyncTests = []
onFail = (description, fn, err) ->
  failures.push
    error: err
    description: description
    source: fn.toString() if fn.toString?

@test = (description, fn) ->
  ++total
  try
    result = fn.call(fn)
    if result instanceof Promise # An async test.
      asyncTests.push result
      result.then ->
        passedTests++
      .catch (err) ->
        onFail description, fn, err
    else
      passedTests++
  catch err
    onFail description, fn, err

@failures =
  push: (failure) -> # Match function called by regular tests
    ++failedTests
    say "#{failure.description}:", 'bad'
    say failure.source, 'subtle' if failure.source?
    say failure.error, 'bad'
    console.error failure.error

@ok = (good, msg = 'Error') ->
  throw Error msg unless good

# Polyfill Node assert’s fail
@fail = ->
  ok no

# Polyfill Node assert’s deepEqual with Underscore’s isEqual
@deepEqual = (a, b) ->
  ok _.isEqual(a, b), "Expected #{JSON.stringify a} to deep equal #{JSON.stringify b}"

# See [http://wiki.ecmascript.org/doku.php?id=harmony:egal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
egal = (a, b) ->
  if a is b
    a isnt 0 or 1/a is 1/b
  else
    a isnt a and b isnt b

# A recursive functional equivalence helper; uses egal for testing equivalence.
arrayEgal = (a, b) ->
  if egal a, b then yes
  else if a instanceof Array and b instanceof Array
    return no unless a.length is b.length
    return no for el, idx in a when not arrayEgal el, b[idx]
    yes

diffOutput = (expectedOutput, actualOutput) ->
  expectedOutputLines = expectedOutput.split '\n'
  actualOutputLines = actualOutput.split '\n'
  for line, i in actualOutputLines
    if line isnt expectedOutputLines[i]
      actualOutputLines[i] = "#{yellow}#{line}#{reset}"
  """Expected generated JavaScript to be:
  #{reset}#{expectedOutput}#{red}
    but instead it was:
  #{reset}#{actualOutputLines.join '\n'}#{red}"""

@eq = (a, b, msg) ->
  ok egal(a, b), msg or
  "Expected #{reset}#{a}#{red} to equal #{reset}#{b}#{red}"

@arrayEq = (a, b, msg) ->
  ok arrayEgal(a, b), msg or
  "Expected #{reset}#{a}#{red} to deep equal #{reset}#{b}#{red}"

@eqJS = (input, expectedOutput, msg) ->
  actualOutput = CoffeeScript.compile input, bare: yes
  .replace /^\s+|\s+$/g, '' # Trim leading/trailing whitespace.
  ok egal(expectedOutput, actualOutput), msg or diffOutput expectedOutput, actualOutput

@isWindows = -> process.platform is 'win32'

@inspect = (obj) ->
  if global.testingBrowser
    JSON.stringify obj, null, 2
  else
    require('util').inspect obj,
      depth: 10
      colors: if process.env.NODE_DISABLE_COLORS then no else yes

# Helpers to get AST nodes for a string of code.
@getAstRoot = getAstRoot = (code) ->
  CoffeeScript.compile code, ast: yes

# The root node is always a `File` node, so for brevity in the tests return its
# children from `program.body`.
getAstExpressions = (code) ->
  ast = getAstRoot code
  ast.program.body

# Many tests want just the root node.
@getAstExpression = (code) ->
  expressionStatementAst = getAstExpressions(code)[0]
  ok expressionStatementAst.type is 'ExpressionStatement', 'Expected ExpressionStatement AST wrapper'
  expressionStatementAst.expression

@getAstStatement = (code) ->
  statement = getAstExpressions(code)[0]
  ok statement.type isnt 'ExpressionStatement', "Didn't expect ExpressionStatement AST wrapper"
  statement

@getAstExpressionOrStatement = (code) ->
  expressionAst = getAstExpressions(code)[0]
  return expressionAst unless expressionAst.type is 'ExpressionStatement'
  expressionAst.expression

@throwsCompileError = (code, compileOpts, args...) ->
  throws -> CoffeeScript.compile code, compileOpts, args...
  throws -> CoffeeScript.compile code, Object.assign({}, (compileOpts ? {}), ast: yes), args...

@doesNotThrowCompileError = (code, compileOpts, args...) ->
  doesNotThrow -> CoffeeScript.compile code, compileOpts, args...
  doesNotThrow -> CoffeeScript.compile code, Object.assign({}, (compileOpts ? {}), ast: yes), args...


@doesNotThrow = (fn) ->
  fn()
  ok yes

@throws = (fun, err, msg) ->
  try
    fun()
  catch e
    if err
      if typeof err is 'function' and e instanceof err # Handle comparing exceptions
        ok yes
      else if e.toString().indexOf('[stdin]') is 0 # Handle comparing error messages
        ok err e
      else if err instanceof RegExp
        ok err.test e
      else
        eq e, err
    else
      ok yes
    return
  ok no


# Run the tests
for test in document.getElementsByClassName 'test'
  say '\u2714 ' + test.id
  options = {}
  options.filename = currentFile = test.id
  options.literate = yes if test.type is 'text/x-literate-coffeescript'
  CoffeeScript.run test.innerHTML, options

# Finish up
done = ->
  yay = passedTests is total and not failedTests
  sec = (new Date - start) / 1000
  msg = "passed #{passedTests} tests in #{sec.toFixed 2} seconds"
  msg = "failed #{total - passedTests} tests and #{msg}" unless yay
  say msg, (if yay then 'good' else 'bad'), 'result'

  gtag 'event', 'tests_complete',
    event_category: 'tests'
    event_label: if yay then 'passed' else 'failed'
    value: if yay then passedTests else total - passedTests
  gtag 'event', 'tests_report',
    event_category: 'tests'
    event_label: msg
  gtag 'event', 'timing_complete',
    name: 'tests_run'
    value: sec * 1000

Promise.all(asyncTests).then(done).catch(done)
</script>

<script type="text/x-coffeescript" class="test" id="abstract_syntax_tree">
# Astract Syntax Tree generation
# ------------------------------

# Recursively compare all values of enumerable properties of `expected` with
# those of `actual`. Use `looseArray` helper function to skip array length
# comparison.
deepStrictIncludeExpectedProperties = (actual, expected) ->
  eq actual.length, expected.length if expected instanceof Array and not expected.loose
  for key, val of expected
    if val? and typeof val is 'object'
      fail "Property #{reset}#{key}#{red} expected, but was missing" unless actual[key]
      deepStrictIncludeExpectedProperties actual[key], val
    else
      eq actual[key], val, """
        Property #{reset}#{key}#{red}: expected #{reset}#{actual[key]}#{red} to equal #{reset}#{val}#{red}
          Expected AST output to include:
          #{reset}#{inspect expected}#{red}
          but instead it was:
          #{reset}#{inspect actual}#{red}
      """
  actual

# Flag array for loose comparison. See reference to `.loose` in
# `deepStrictIncludeExpectedProperties` above.
looseArray = (arr) ->
  Object.defineProperty arr, 'loose',
    value: yes
    enumerable: no
  arr

testAgainstExpected = (ast, expected) ->
  if expected?
    deepStrictIncludeExpectedProperties ast, expected
  else
    # Convenience for creating new tests; call `testExpression` with no second
    # parameter to see what the current AST generation is for your input code.
    console.log inspect ast

testExpression = (code, expected) ->
  ast = getAstExpression code
  testAgainstExpected ast, expected

testStatement = (code, expected) ->
  ast = getAstStatement code
  testAgainstExpected ast, expected

testComments = (code, expected) ->
  ast = getAstRoot code
  testAgainstExpected ast.comments, expected

test 'Confirm functionality of `deepStrictIncludeExpectedProperties`', ->
  actual =
    name: 'Name'
    a:
      b: 1
      c: 2
    x: [1, 2, 3]

  check = (message, test, expected) ->
    test (-> deepStrictIncludeExpectedProperties actual, expected), null, message

  check 'Expected property does not match', throws,
    name: '"Name"'

  check 'Array length mismatch', throws,
    x: [1, 2]

  check 'Skip array length check', doesNotThrow,
    x: looseArray [
      1
      2
    ]

  check 'Array length matches', doesNotThrow,
    x: [1, 2, 3]

  check 'Array prop mismatch', throws,
    x: [3, 2, 1]

  check 'Partial object comparison', doesNotThrow,
    a:
      c: 2
    forbidden: undefined

  check 'Actual has forbidden prop', throws,
    a:
      b: 1
      c: undefined

  check 'Check prop for existence only', doesNotThrow,
    name: {}
    a: {}
    x: {}

  check 'Prop is missing', throws,
    missingProp: {}

# Shorthand helpers for common AST patterns.

EMPTY_BLOCK =
  type: 'BlockStatement'
  body: []
  directives: []

ID = (name, additionalProperties = {}) ->
  Object.assign({
    type: 'Identifier'
    name
  }, additionalProperties)

NUMBER = (value) -> {
  type: 'NumericLiteral'
  value
}

STRING = (value) -> {
  type: 'StringLiteral'
  value
}

# Check each node type in the same order as they appear in `nodes.coffee`.
# For nodes that have equivalents in Babel’s AST spec, we’re checking that
# the type and properties match. When relevant, also check that values of
# properties are as expected.

test "AST as expected for Block node", ->
  deepStrictIncludeExpectedProperties CoffeeScript.compile('a', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      # sourceType: 'module'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
      ]
      directives: []
    comments: []

  deepStrictIncludeExpectedProperties CoffeeScript.compile('# comment', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      body: []
      directives: []
    comments: [
      type: 'CommentLine'
      value: ' comment'
    ]

  deepStrictIncludeExpectedProperties CoffeeScript.compile('', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      body: []
      directives: []

  deepStrictIncludeExpectedProperties CoffeeScript.compile(' ', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      body: []
      directives: []

test "AST as expected for NumberLiteral node", ->
  testExpression '42',
    type: 'NumericLiteral'
    value: 42
    extra:
      rawValue: 42
      raw: '42'

  testExpression '0xE1',
    type: 'NumericLiteral'
    value: 225
    extra:
      rawValue: 225
      raw: '0xE1'

  testExpression '10_000',
    type: 'NumericLiteral'
    value: 10000
    extra:
      rawValue: 10000
      raw: '10_000'

  testExpression '1_2.34_5e6_7',
    type: 'NumericLiteral'
    value: 12.345e67
    extra:
      rawValue: 12.345e67
      raw: '1_2.34_5e6_7'

  testExpression '0o7_7_7',
    type: 'NumericLiteral'
    value: 0o777
    extra:
      rawValue: 0o777
      raw: '0o7_7_7'

  testExpression '42n',
    type: 'BigIntLiteral'
    value: '42'
    extra:
      rawValue: '42'
      raw: '42n'

  testExpression '2e3_08',
    type: 'NumericLiteral'
    value: Infinity
    extra:
      rawValue: Infinity
      raw: '2e3_08'

test "AST as expected for InfinityLiteral node", ->
  testExpression 'Infinity',
    type: 'Identifier'
    name: 'Infinity'

  testExpression '2e308',
    type: 'NumericLiteral'
    value: Infinity
    extra:
      raw: '2e308'
      rawValue: Infinity

test "AST as expected for NaNLiteral node", ->
  testExpression 'NaN',
    type: 'Identifier'
    name: 'NaN'

test "AST as expected for StringLiteral node", ->
  # Just a standalone string literal would be treated as a directive,
  # so embed the string literal in an enclosing expression (e.g. a call).
  testExpression 'a "string cheese"',
    type: 'CallExpression'
    arguments: [
      type: 'StringLiteral'
      value: 'string cheese'
      extra:
        raw: '"string cheese"'
    ]

  testExpression "b 'cheese string'",
    type: 'CallExpression'
    arguments: [
      type: 'StringLiteral'
      value: 'cheese string'
      extra:
        raw: "'cheese string'"
    ]

  testExpression "'''heredoc'''",
    type: 'TemplateLiteral'
    expressions: []
    quasis: [
      type: 'TemplateElement'
      value:
        raw: 'heredoc'
      tail: yes
    ]
    quote: "'''"

test "AST as expected for PassthroughLiteral node", ->
  code = 'const CONSTANT = "unreassignable!"'
  testExpression "`#{code}`",
    type: 'PassthroughLiteral'
    value: code
    here: no

  code = '\nconst CONSTANT = "unreassignable!"\n'
  testExpression "```#{code}```",
    type: 'PassthroughLiteral'
    value: code
    here: yes

  testExpression "``",
    type: 'PassthroughLiteral'
    value: ''
    here: no

  # escaped backticks
  testExpression "`\\`abc\\``",
    type: 'PassthroughLiteral'
    value: '\\`abc\\`'
    here: no

test "AST as expected for IdentifierLiteral node", ->
  testExpression 'id',
    type: 'Identifier'
    name: 'id'

test "AST as expected for JSXTag node", ->
  testExpression '<CSXY />',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'CSXY'
      attributes: []
      selfClosing: yes
    closingElement: null
    children: []

  testExpression '<div></div>',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
    children: []

  testExpression '<A.B />',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXMemberExpression'
        object:
          type: 'JSXIdentifier'
          name: 'A'
        property:
          type: 'JSXIdentifier'
          name: 'B'
      attributes: []
      selfClosing: yes
    closingElement: null
    children: []

  testExpression '<Tag.Name.Here></Tag.Name.Here>',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXMemberExpression'
        object:
          type: 'JSXMemberExpression'
          object:
            type: 'JSXIdentifier'
            name: 'Tag'
          property:
            type: 'JSXIdentifier'
            name: 'Name'
        property:
          type: 'JSXIdentifier'
          name: 'Here'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXMemberExpression'
        object:
          type: 'JSXMemberExpression'
          object:
            type: 'JSXIdentifier'
            name: 'Tag'
          property:
            type: 'JSXIdentifier'
            name: 'Name'
        property:
          type: 'JSXIdentifier'
          name: 'Here'
    children: []

  testExpression '<></>',
    type: 'JSXFragment'
    openingFragment:
      type: 'JSXOpeningFragment'
    closingFragment:
      type: 'JSXClosingFragment'
    children: []

  testExpression '<div a b="c" d={e} {...f} />',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
      attributes: [
        type: 'JSXAttribute'
        name:
          type: 'JSXIdentifier'
          name: 'a'
      ,
        type: 'JSXAttribute'
        name:
          type: 'JSXIdentifier'
          name: 'b'
        value:
          type: 'StringLiteral'
          value: 'c'
      ,
        type: 'JSXAttribute'
        name:
          type: 'JSXIdentifier'
          name: 'd'
        value:
          type: 'JSXExpressionContainer'
          expression:
            type: 'Identifier'
            name: 'e'
      ,
        type: 'JSXSpreadAttribute'
        argument:
          type: 'Identifier'
          name: 'f'
        postfix: no
      ]
      selfClosing: yes
    closingElement: null
    children: []

  testExpression '<div {f...} />',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      attributes: [
        type: 'JSXSpreadAttribute'
        argument:
          type: 'Identifier'
          name: 'f'
        postfix: yes
      ]

  testExpression '<div>abc</div>',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
    children: [
      type: 'JSXText'
      extra:
        raw: 'abc'
      value: 'abc'
    ]

  testExpression '''
    <a>
      {b}
      <c />
    </a>
  ''',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'a'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'a'
    children: [
      type: 'JSXText'
      extra:
        raw: '\n  '
      value: '\n  '
    ,
      type: 'JSXExpressionContainer'
      expression: ID 'b'
    ,
      type: 'JSXText'
      extra:
        raw: '\n  '
      value: '\n  '
    ,
      type: 'JSXElement'
      openingElement:
        type: 'JSXOpeningElement'
        name:
          type: 'JSXIdentifier'
          name: 'c'
        selfClosing: true
      closingElement: null
      children: []
    ,
      type: 'JSXText'
      extra:
        raw: '\n'
      value: '\n'
    ]

  testExpression '<>abc{}</>',
    type: 'JSXFragment'
    openingFragment:
      type: 'JSXOpeningFragment'
    closingFragment:
      type: 'JSXClosingFragment'
    children: [
      type: 'JSXText'
      extra:
        raw: 'abc'
      value: 'abc'
    ,
      type: 'JSXExpressionContainer'
      expression:
        type: 'JSXEmptyExpression'
    ]

  testExpression '''
    <a>{<b />}</a>
  ''',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'a'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'a'
    children: [
      type: 'JSXExpressionContainer'
      expression:
        type: 'JSXElement'
        openingElement:
          type: 'JSXOpeningElement'
          name:
            type: 'JSXIdentifier'
            name: 'b'
          selfClosing: true
        closingElement: null
        children: []
    ]

  testExpression '''
    <div>{
      # comment
    }</div>
  ''',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
    children: [
      type: 'JSXExpressionContainer'
      expression:
        type: 'JSXEmptyExpression'
    ]

  testExpression '''
    <div>{### here ###}</div>
  ''',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
      attributes: []
      selfClosing: no
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXIdentifier'
        name: 'div'
    children: [
      type: 'JSXExpressionContainer'
      expression:
        type: 'JSXEmptyExpression'
    ]

  testExpression '<div:a b:c />',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXNamespacedName'
        namespace:
          type: 'JSXIdentifier'
          name: 'div'
        name:
          type: 'JSXIdentifier'
          name: 'a'
      attributes: [
        type: 'JSXAttribute'
        name:
          type: 'JSXNamespacedName'
          namespace:
            type: 'JSXIdentifier'
            name: 'b'
          name:
            type: 'JSXIdentifier'
            name: 'c'
      ]
      selfClosing: yes

  testExpression '''
    <div:a>
      {b}
    </div:a>
  ''',
    type: 'JSXElement'
    openingElement:
      type: 'JSXOpeningElement'
      name:
        type: 'JSXNamespacedName'
        namespace:
          type: 'JSXIdentifier'
          name: 'div'
        name:
          type: 'JSXIdentifier'
          name: 'a'
    closingElement:
      type: 'JSXClosingElement'
      name:
        type: 'JSXNamespacedName'
        namespace:
          type: 'JSXIdentifier'
          name: 'div'
        name:
          type: 'JSXIdentifier'
          name: 'a'

  testExpression '''
    <div b={
      c
      d
    } />
  ''',
    type: 'JSXElement'
    openingElement:
      attributes: [
        value:
          type: 'JSXExpressionContainer'
          expression:
            type: 'BlockStatement'
            body: [
              type: 'ExpressionStatement'
            ,
              type: 'ExpressionStatement'
              expression:
                returns: yes
            ]
      ]

test "AST as expected for ComputedPropertyName node", ->
  testExpression '[fn]: ->',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'fn'
      value:
        type: 'FunctionExpression'
      computed: yes
      shorthand: no
      method: no
    ]
    implicit: yes

  testExpression '[a]: b',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'a'
      value:
        type: 'Identifier'
        name: 'b'
      computed: yes
      shorthand: no
      method: no
    ]
    implicit: yes

test "AST as expected for StatementLiteral node", ->
  testStatement 'break',
    type: 'BreakStatement'

  testStatement 'continue',
    type: 'ContinueStatement'

  testStatement 'debugger',
    type: 'DebuggerStatement'

test "AST as expected for ThisLiteral node", ->
  testExpression 'this',
    type: 'ThisExpression'
    shorthand: no

  testExpression '@',
    type: 'ThisExpression'
    shorthand: yes

  testExpression '@b',
    type: 'MemberExpression'
    object:
      type: 'ThisExpression'
      shorthand: yes
    property: ID 'b'

  testExpression 'this.b',
    type: 'MemberExpression'
    object:
      type: 'ThisExpression'
      shorthand: no
    property: ID 'b'

test "AST as expected for UndefinedLiteral node", ->
  testExpression 'undefined',
    type: 'Identifier'
    name: 'undefined'

test "AST as expected for NullLiteral node", ->
  testExpression 'null',
    type: 'NullLiteral'

test "AST as expected for BooleanLiteral node", ->
  testExpression 'true',
    type: 'BooleanLiteral'
    value: true
    name: 'true'

  testExpression 'off',
    type: 'BooleanLiteral'
    value: false
    name: 'off'

  testExpression 'yes',
    type: 'BooleanLiteral'
    value: true
    name: 'yes'

test "AST as expected for Return node", ->
  testStatement 'return no',
    type: 'ReturnStatement'
    argument:
      type: 'BooleanLiteral'

  testExpression '''
    (a, b) ->
      return a + b
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ReturnStatement'
        argument:
          type: 'BinaryExpression'
      ]

  testExpression '-> return',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ReturnStatement'
        argument: null
      ]

test "AST as expected for YieldReturn node", ->
  testExpression '-> yield return 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'YieldExpression'
          argument:
            type: 'ReturnStatement'
            argument: NUMBER 1
          delegate: no
      ]

test "AST as expected for AwaitReturn node", ->
  testExpression '-> await return 2',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AwaitExpression'
          argument:
            type: 'ReturnStatement'
            argument: NUMBER 2
      ]

test "AST as expected for Call node", ->
  testExpression 'fn()',
    type: 'CallExpression'
    callee:
      type: 'Identifier'
      name: 'fn'
    arguments: []
    optional: no
    implicit: no
    returns: undefined

  testExpression 'new Date()',
    type: 'NewExpression'
    callee:
      type: 'Identifier'
      name: 'Date'
    arguments: []
    optional: no
    implicit: no

  testExpression 'new Date?()',
    type: 'NewExpression'
    callee:
      type: 'Identifier'
      name: 'Date'
    arguments: []
    optional: yes
    implicit: no

  testExpression 'new Old',
    type: 'NewExpression'
    callee:
      type: 'Identifier'
      name: 'Old'
    arguments: []
    optional: no
    implicit: no

  testExpression 'new Old(1)',
    type: 'NewExpression'
    callee:
      type: 'Identifier'
      name: 'Old'
    arguments: [
      type: 'NumericLiteral'
      value: 1
    ]
    optional: no
    implicit: no

  testExpression 'new Old 1',
    type: 'NewExpression'
    callee:
      type: 'Identifier'
      name: 'Old'
    arguments: [
      type: 'NumericLiteral'
      value: 1
    ]
    optional: no
    implicit: yes

  testExpression 'maybe?()',
    type: 'OptionalCallExpression'
    optional: yes
    implicit: no

  testExpression 'maybe?(1 + 1)',
    type: 'OptionalCallExpression'
    arguments: [
      type: 'BinaryExpression'
    ]
    optional: yes
    implicit: no

  testExpression 'maybe? 1 + 1',
    type: 'OptionalCallExpression'
    arguments: [
      type: 'BinaryExpression'
    ]
    optional: yes
    implicit: yes

  testExpression 'goDo this, that',
    type: 'CallExpression'
    arguments: [
      type: 'ThisExpression'
    ,
      type: 'Identifier'
      name: 'that'
    ]
    implicit: yes
    optional: no

  testExpression 'a?().b',
    type: 'OptionalMemberExpression'
    object:
      type: 'OptionalCallExpression'
      optional: yes
    optional: no

  testExpression 'a?.b.c()',
    type: 'OptionalCallExpression'
    callee:
      type: 'OptionalMemberExpression'
      object:
        type: 'OptionalMemberExpression'
        optional: yes
      optional: no
    optional: no

  testExpression 'a?.b?()',
    type: 'OptionalCallExpression'
    callee:
      type: 'OptionalMemberExpression'
      optional: yes
    optional: yes

  testExpression 'a?().b?()',
    type: 'OptionalCallExpression'
    callee:
      type: 'OptionalMemberExpression'
      optional: no
      object:
        type: 'OptionalCallExpression'
        optional: yes
    optional: yes

  testExpression 'a().b?()',
    type: 'OptionalCallExpression'
    callee:
      type: 'MemberExpression'
      optional: no
      object:
        type: 'CallExpression'
        optional: no
    optional: yes

  testExpression 'a?().b()',
    type: 'OptionalCallExpression'
    callee:
      type: 'OptionalMemberExpression'
      optional: no
      object:
        type: 'OptionalCallExpression'
        optional: yes
    optional: no

test "AST as expected for SuperCall node", ->
  testStatement 'class child extends parent then constructor: -> super()',
    type: 'ClassDeclaration'
    body:
      type: 'ClassBody'
      body: [
        body:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression:
              type: 'CallExpression'
              callee:
                type: 'Super'
          ]
      ]

test "AST as expected for Super node", ->
  testStatement 'class child extends parent then func: -> super.prop',
    type: 'ClassDeclaration'
    body:
      type: 'ClassBody'
      body: [
        body:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression:
              type: 'MemberExpression'
              object:
                type: 'Super'
              property: ID 'prop'
              computed: no
          ]
      ]

  testStatement '''
    class child extends parent
      func: ->
        super[prop]()
  ''',
    type: 'ClassDeclaration'
    body:
      type: 'ClassBody'
      body: [
        body:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression:
              type: 'CallExpression'
              callee:
                type: 'MemberExpression'
                object:
                  type: 'Super'
                property: ID 'prop'
                computed: yes
          ]
      ]

test "AST as expected for RegexWithInterpolations node", ->
  testExpression '///^#{flavor}script$///',
    type: 'InterpolatedRegExpLiteral'
    interpolatedPattern:
      type: 'TemplateLiteral'
      expressions: [
        ID 'flavor'
      ]
      quasis: [
        type: 'TemplateElement'
        value:
          raw: '^'
        tail: no
      ,
        type: 'TemplateElement'
        value:
          raw: 'script$'
        tail: yes
      ]
      quote: '///'
    flags: ''

  testExpression '''
    ///
      a
      #{b}///ig
  ''',
    type: 'InterpolatedRegExpLiteral'
    interpolatedPattern:
      type: 'TemplateLiteral'
      expressions: [
        ID 'b'
      ]
      quasis: [
        type: 'TemplateElement'
        value:
          raw: '\n  a\n  '
        tail: no
      ,
        type: 'TemplateElement'
        value:
          raw: ''
        tail: yes
      ]
      quote: '///'
    flags: 'ig'

  testExpression '''
    ///
      a # first
      #{b} ### second ###
    ///ig
  ''',
    type: 'InterpolatedRegExpLiteral'
    interpolatedPattern:
      type: 'TemplateLiteral'
      expressions: [
        ID 'b'
      ]
      quasis: [
        type: 'TemplateElement'
        value:
          raw: '\n  a # first\n  '
        tail: no
      ,
        type: 'TemplateElement'
        value:
          raw: ' ### second ###\n'
        tail: yes
      ]
      quote: '///'
    flags: 'ig'
    comments: [
      type: 'CommentLine'
      value: ' first'
    ,
      type: 'CommentBlock'
      value: ' second '
    ]

test "AST as expected for TaggedTemplateCall node", ->
  testExpression 'func"tagged"',
    type: 'TaggedTemplateExpression'
    tag: ID 'func'
    quasi:
      type: 'TemplateLiteral'
      expressions: []
      quasis: [
        type: 'TemplateElement'
        value:
          raw: 'tagged'
        tail: yes
      ]

  testExpression 'a"b#{c}"',
    type: 'TaggedTemplateExpression'
    tag: ID 'a'
    quasi:
      type: 'TemplateLiteral'
      expressions: [
        ID 'c'
      ]
      quasis: [
        type: 'TemplateElement'
        value:
          raw: 'b'
        tail: no
      ,
        type: 'TemplateElement'
        value:
          raw: ''
        tail: yes
      ]

  testExpression '''
    a"""
      b#{c}
    """
  ''',
    type: 'TaggedTemplateExpression'
    tag: ID 'a'
    quasi:
      type: 'TemplateLiteral'
      expressions: [
        ID 'c'
      ]
      quasis: [
        type: 'TemplateElement'
        value:
          raw: '\n  b'
        tail: no
      ,
        type: 'TemplateElement'
        value:
          raw: '\n'
        tail: yes
      ]

  testExpression """
    a'''
      b
    '''
  """,
    type: 'TaggedTemplateExpression'
    tag: ID 'a'
    quasi:
      type: 'TemplateLiteral'
      expressions: []
      quasis: [
        type: 'TemplateElement'
        value:
          raw: '\n  b\n'
        tail: yes
      ]

test "AST as expected for Access node", ->
  testExpression 'obj.prop',
    type: 'MemberExpression'
    object:
      type: 'Identifier'
      name: 'obj'
    property:
      type: 'Identifier'
      name: 'prop'
    computed: no
    optional: no
    shorthand: no

  testExpression 'obj?.prop',
    type: 'OptionalMemberExpression'
    object:
      type: 'Identifier'
      name: 'obj'
    property:
      type: 'Identifier'
      name: 'prop'
    computed: no
    optional: yes
    shorthand: no

  testExpression 'a::b',
    type: 'MemberExpression'
    object:
      type: 'MemberExpression'
      object:
        type: 'Identifier'
        name: 'a'
      property:
        type: 'Identifier'
        name: 'prototype'
      computed: no
      optional: no
      shorthand: yes
    property:
      type: 'Identifier'
      name: 'b'
    computed: no
    optional: no
    shorthand: no

  testExpression 'a.prototype.b',
    type: 'MemberExpression'
    object:
      type: 'MemberExpression'
      object:
        type: 'Identifier'
        name: 'a'
      property:
        type: 'Identifier'
        name: 'prototype'
      computed: no
      optional: no
      shorthand: no
    property:
      type: 'Identifier'
      name: 'b'
    computed: no
    optional: no
    shorthand: no

  testExpression 'a?.b.c',
    type: 'OptionalMemberExpression'
    object:
      type: 'OptionalMemberExpression'
      object:
        type: 'Identifier'
        name: 'a'
      property:
        type: 'Identifier'
        name: 'b'
      computed: no
      optional: yes
      shorthand: no
    property:
      type: 'Identifier'
      name: 'c'
    computed: no
    optional: no
    shorthand: no

test "AST as expected for Index node", ->
  testExpression 'a[b]',
    type: 'MemberExpression'
    object:
      type: 'Identifier'
      name: 'a'
    property:
      type: 'Identifier'
      name: 'b'
    computed: yes
    optional: no
    shorthand: no

  testExpression 'a?[b]',
    type: 'OptionalMemberExpression'
    object:
      type: 'Identifier'
      name: 'a'
    property:
      type: 'Identifier'
      name: 'b'
    computed: yes
    optional: yes
    shorthand: no

  testExpression 'a::[b]',
    type: 'MemberExpression'
    object:
      type: 'MemberExpression'
      object:
        type: 'Identifier'
        name: 'a'
      property:
        type: 'Identifier'
        name: 'prototype'
      computed: no
      optional: no
      shorthand: yes
    property:
      type: 'Identifier'
      name: 'b'
    computed: yes
    optional: no
    shorthand: no

  testExpression 'a[b][3]',
    type: 'MemberExpression'
    object:
      type: 'MemberExpression'
      object:
        type: 'Identifier'
        name: 'a'
      property:
        type: 'Identifier'
        name: 'b'
      computed: yes
      optional: no
      shorthand: no
    property:
      type: 'NumericLiteral'
      value: 3
    computed: yes
    optional: no
    shorthand: no

  testExpression 'a[if b then c]',
    type: 'MemberExpression'
    object: ID 'a'
    property:
      type: 'ConditionalExpression'
      test: ID 'b'
      consequent: ID 'c'
    computed: yes
    optional: no
    shorthand: no

test "AST as expected for Range node", ->
  testExpression '[x..y]',
    type: 'Range'
    exclusive: no
    from:
      name: 'x'
    to:
      name: 'y'

  testExpression '[4...2]',
    type: 'Range'
    exclusive: yes
    from:
      value: 4
    to:
      value: 2

test "AST as expected for Slice node", ->
  testExpression 'x[..y]',
    property:
      type: 'Range'
      exclusive: no
      from: null
      to:
        name: 'y'

  testExpression 'x[y...]',
    property:
      type: 'Range'
      exclusive: yes
      from:
        name: 'y'
      to: null

  testExpression 'x[...]',
    property:
      type: 'Range'
      exclusive: yes
      from: null
      to: null

  testExpression '"abc"[...2]',
    type: 'MemberExpression'
    property:
      type: 'Range'
      from: null
      to:
        type: 'NumericLiteral'
        value: 2
      exclusive: yes
    computed: yes
    optional: no
    shorthand: no

  testExpression 'x[...][a..][b...][..c][...d]',
    type: 'MemberExpression'
    object:
      type: 'MemberExpression'
      object:
        type: 'MemberExpression'
        object:
          type: 'MemberExpression'
          object:
            type: 'MemberExpression'
            property:
              type: 'Range'
              from: null
              to: null
              exclusive: yes
          property:
            type: 'Range'
            from:
              name: 'a'
            to: null
            exclusive: no
        property:
          type: 'Range'
          from:
            name: 'b'
          to: null
          exclusive: yes
      property:
        type: 'Range'
        from: null
        to:
          name: 'c'
        exclusive: no
    property:
      type: 'Range'
      from: null
      to:
        name: 'd'
      exclusive: yes

test "AST as expected for Obj node", ->
  testExpression "{a: 1, b, [c], @d, [e()]: f, 'g': 2, ...h, i...}",
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'a'
      value:
        type: 'NumericLiteral'
        value: 1
      computed: no
      shorthand: no
    ,
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'b'
      value:
        type: 'Identifier'
        name: 'b'
      computed: no
      shorthand: yes
    ,
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'c'
      value:
        type: 'Identifier'
        name: 'c'
      computed: yes
      shorthand: yes
    ,
      type: 'ObjectProperty'
      key:
        type: 'MemberExpression'
        object:
          type: 'ThisExpression'
        property:
          type: 'Identifier'
          name: 'd'
      value:
        type: 'MemberExpression'
        object:
          type: 'ThisExpression'
        property:
          type: 'Identifier'
          name: 'd'
      computed: no
      shorthand: yes
    ,
      type: 'ObjectProperty'
      key:
        type: 'CallExpression'
        callee:
          type: 'Identifier'
          name: 'e'
        arguments: []
      value:
        type: 'Identifier'
        name: 'f'
      computed: yes
      shorthand: no
    ,
      type: 'ObjectProperty'
      key:
        type: 'StringLiteral'
        value: 'g'
      value:
        type: 'NumericLiteral'
        value: 2
      computed: no
      shorthand: no
    ,
      type: 'SpreadElement'
      argument:
        type: 'Identifier'
        name: 'h'
      postfix: no
    ,
      type: 'SpreadElement'
      argument:
        type: 'Identifier'
        name: 'i'
      postfix: yes
    ]
    implicit: no

  testExpression 'a: 1',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key:
        type: 'Identifier'
        name: 'a'
      value:
        type: 'NumericLiteral'
        value: 1
      shorthand: no
      computed: no
    ]
    implicit: yes

  testExpression '''
    a:
      if b then c
  ''',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key: ID 'a'
      value:
        type: 'ConditionalExpression'
        test: ID 'b'
        consequent: ID 'c'
    ]
    implicit: yes

  testExpression '''
    a:
      c if b
  ''',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key: ID 'a'
      value:
        type: 'ConditionalExpression'
        test: ID 'b'
        consequent: ID 'c'
    ]
    implicit: yes

  testExpression '"#{a}": 1',
    type: 'ObjectExpression'
    properties: [
      type: 'ObjectProperty'
      key:
        type: 'TemplateLiteral'
        expressions: [
          ID 'a'
        ]
      value:
        type: 'NumericLiteral'
        value: 1
      shorthand: no
      computed: yes
    ]
    implicit: yes

test "AST as expected for Arr node", ->
  testExpression '[]',
    type: 'ArrayExpression'
    elements: []

  testExpression '[3, tables, !1]',
    type: 'ArrayExpression'
    elements: [
      {value: 3}
      {name: 'tables'}
      {operator: '!'}
    ]

test "AST as expected for Class node", ->
  testStatement 'class Klass',
    type: 'ClassDeclaration'
    id: ID 'Klass', declaration: yes
    superClass: null
    body:
      type: 'ClassBody'
      body: []

  testStatement 'class child extends parent',
    type: 'ClassDeclaration'
    id: ID 'child', declaration: yes
    superClass: ID 'parent', declaration: no
    body:
      type: 'ClassBody'
      body: []

  testStatement 'class Klass then constructor: -> @a = 1',
    type: 'ClassDeclaration'
    id: ID 'Klass', declaration: yes
    superClass: null
    body:
      type: 'ClassBody'
      body: [
        type: 'ClassMethod'
        static: no
        key: ID 'constructor', declaration: no
        computed: no
        kind: 'constructor'
        id: null
        generator: no
        async: no
        params: []
        body:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression:
              type: 'AssignmentExpression'
              returns: undefined
          ]
        bound: no
      ]

  testExpression '''
    a = class A
      b: ->
        c
      d: =>
        e
  ''',
    type: 'AssignmentExpression'
    right:
      type: 'ClassExpression'
      id: ID 'A', declaration: yes
      superClass: null
      body:
        type: 'ClassBody'
        body: [
          type: 'ClassMethod'
          static: no
          key: ID 'b'
          computed: no
          kind: 'method'
          id: null
          generator: no
          async: no
          params: []
          body:
            type: 'BlockStatement'
            body: [
              type: 'ExpressionStatement'
              expression: ID 'c', returns: yes
            ]
          operator: ':'
          bound: no
        ,
          type: 'ClassMethod'
          static: no
          key: ID 'd'
          computed: no
          kind: 'method'
          id: null
          generator: no
          async: no
          params: []
          body:
            type: 'BlockStatement'
            body: [
              type: 'ExpressionStatement'
              expression: ID 'e'
            ]
          operator: ':'
          bound: yes
        ]

  testStatement '''
    class A
      @b: ->
      @c = =>
      @d: 1
      @e = 2
      j = 5
      A.f = 3
      A.g = ->
      this.h = ->
      this.i = 4
  ''',
    type: 'ClassDeclaration'
    id: ID 'A', declaration: yes
    superClass: null
    body:
      type: 'ClassBody'
      body: [
        type: 'ClassMethod'
        static: yes
        key: ID 'b'
        computed: no
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: ':'
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
        bound: no
      ,
        type: 'ClassMethod'
        static: yes
        key: ID 'c'
        computed: no
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: '='
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
        bound: yes
      ,
        type: 'ClassProperty'
        static: yes
        key: ID 'd'
        computed: no
        value: NUMBER 1
        operator: ':'
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
      ,
        type: 'ClassProperty'
        static: yes
        key: ID 'e'
        computed: no
        value: NUMBER 2
        operator: '='
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left: ID 'j', declaration: yes
          right: NUMBER 5
      ,
        type: 'ClassProperty'
        static: yes
        key: ID 'f'
        computed: no
        value: NUMBER 3
        operator: '='
        staticClassName: ID 'A'
      ,
        type: 'ClassMethod'
        static: yes
        key: ID 'g'
        computed: no
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: '='
        staticClassName: ID 'A'
        bound: no
      ,
        type: 'ClassMethod'
        static: yes
        key: ID 'h'
        computed: no
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: '='
        staticClassName:
          type: 'ThisExpression'
          shorthand: no
        bound: no
      ,
        type: 'ClassProperty'
        static: yes
        key: ID 'i'
        computed: no
        value: NUMBER 4
        operator: '='
        staticClassName:
          type: 'ThisExpression'
          shorthand: no
      ]

  testStatement '''
    class A
      b: 1
      [c]: 2
      [d]: ->
      @[e]: ->
      @[f]: 3
  ''',
    type: 'ClassDeclaration'
    id: ID 'A', declaration: yes
    superClass: null
    body:
      type: 'ClassBody'
      body: [
        type: 'ClassPrototypeProperty'
        key: ID 'b', declaration: no
        value: NUMBER 1
        computed: no
      ,
        type: 'ClassPrototypeProperty'
        key: ID 'c'
        value: NUMBER 2
        computed: yes
      ,
        type: 'ClassMethod'
        static: no
        key: ID 'd'
        computed: yes
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: ':'
        bound: no
      ,
        type: 'ClassMethod'
        static: yes
        key: ID 'e'
        computed: yes
        kind: 'method'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        operator: ':'
        bound: no
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
      ,
        type: 'ClassProperty'
        static: yes
        key: ID 'f'
        computed: yes
        value: NUMBER 3
        operator: ':'
        staticClassName:
          type: 'ThisExpression'
          shorthand: yes
      ]

  testStatement '''
    class A
      @[b] = ->
      "#{c}": ->
      @[d] = 1
      [e]: 2
      "#{f}": 3
      @[g]: 4
  ''',
    type: 'ClassDeclaration'
    body:
      body: [
        type: 'ClassMethod'
        computed: yes
      ,
        type: 'ClassMethod'
        computed: yes
      ,
        type: 'ClassProperty'
        computed: yes
      ,
        type: 'ClassPrototypeProperty'
        computed: yes
      ,
        type: 'ClassPrototypeProperty'
        computed: yes
      ,
        type: 'ClassProperty'
        computed: yes
      ]

  testStatement '''
    class A.b
  ''',
    type: 'ClassDeclaration'
    id:
      type: 'MemberExpression'
      object: ID 'A', declaration: no
      property: ID 'b', declaration: no

  testStatement '''
    class A
      'constructor': ->
  ''',
    type: 'ClassDeclaration'
    body:
      type: 'ClassBody'
      body: [
        type: 'ClassMethod'
        static: no
        key:
          type: 'StringLiteral'
        computed: no
        kind: 'constructor'
        id: null
        generator: no
        async: no
        params: []
        body: EMPTY_BLOCK
        bound: no
      ]


test "AST as expected for ModuleDeclaration node", ->
  testStatement 'export {X}',
    type: 'ExportNamedDeclaration'
    declaration: null
    specifiers: [
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'X'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'X'
        declaration: no
    ]
    source: null
    exportKind: 'value'

  testStatement 'import X from "."',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportDefaultSpecifier'
      local:
        type: 'Identifier'
        name: 'X'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: '.'

  testStatement 'import X from "." assert { type: "json" }',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportDefaultSpecifier'
      local:
        type: 'Identifier'
        name: 'X'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: '.'
    assertions: [
      type: 'ImportAttribute'
      key:
        type: 'Identifier'
        name: 'type'
      value:
        type: 'StringLiteral'
        value: 'json'
        extra:
          raw: '"json"'
    ]

test "AST as expected for ImportDeclaration node", ->
  testStatement 'import React, {Component} from "react"',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportDefaultSpecifier'
      local:
        type: 'Identifier'
        name: 'React'
        declaration: no
    ,
      type: 'ImportSpecifier'
      imported:
        type: 'Identifier'
        name: 'Component'
        declaration: no
      importKind: null
      local:
        type: 'Identifier'
        name: 'Component'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: 'react'
      extra:
        raw: '"react"'

test "AST as expected for ExportNamedDeclaration node", ->
  testStatement 'export {}',
    type: 'ExportNamedDeclaration'
    declaration: null
    specifiers: []
    source: null
    exportKind: 'value'

  testStatement 'export fn = ->',
    type: 'ExportNamedDeclaration'
    declaration:
      type: 'AssignmentExpression'
      left:
        type: 'Identifier'
        declaration: yes
      right:
        type: 'FunctionExpression'
    specifiers: []
    source: null
    exportKind: 'value'

  testStatement 'export class A',
    type: 'ExportNamedDeclaration'
    declaration:
      type: 'ClassDeclaration'
      id: ID 'A', declaration: yes
      superClass: null
      body:
        type: 'ClassBody'
        body: []
    specifiers: []
    source: null
    exportKind: 'value'

  testStatement 'export {x as y, z as default}',
    type: 'ExportNamedDeclaration'
    declaration: null
    specifiers: [
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'x'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'y'
        declaration: no
    ,
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'z'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'default'
        declaration: no
    ]
    source: null
    exportKind: 'value'

  testStatement 'export {default, default as b} from "./abc"',
    type: 'ExportNamedDeclaration'
    declaration: null
    specifiers: [
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'default'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'default'
        declaration: no
    ,
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'default'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'b'
        declaration: no
    ]
    source:
      type: 'StringLiteral'
      value: './abc'
      extra:
        raw: '"./abc"'
    exportKind: 'value'

test "AST as expected for ExportDefaultDeclaration node", ->
  testStatement 'export default class',
    type: 'ExportDefaultDeclaration'
    declaration:
      type: 'ClassDeclaration'

  testStatement 'export default "abc"',
    type: 'ExportDefaultDeclaration'
    declaration:
      type: 'StringLiteral'
      value: 'abc'
      extra:
        raw: '"abc"'

  testStatement 'export default a = b',
    type: 'ExportDefaultDeclaration'
    declaration:
      type: 'AssignmentExpression'
      left: ID 'a', declaration: yes
      right: ID 'b', declaration: no

test "AST as expected for ExportAllDeclaration node", ->
  testStatement 'export * from "module-name"',
    type: 'ExportAllDeclaration'
    source:
      type: 'StringLiteral'
      value: 'module-name'
      extra:
        raw: '"module-name"'
    exportKind: 'value'

  testStatement 'export * from "module-name" assert { type: "json" }',
    type: 'ExportAllDeclaration'
    source:
      type: 'StringLiteral'
      value: 'module-name'
      extra:
        raw: '"module-name"'
    assertions: [
      type: 'ImportAttribute'
      key:
        type: 'Identifier'
        name: 'type'
      value:
        type: 'StringLiteral'
        value: 'json'
        extra:
          raw: '"json"'
    ]
    exportKind: 'value'

test "AST as expected for ExportSpecifierList node", ->
  testStatement 'export {a, b, c}',
    type: 'ExportNamedDeclaration'
    declaration: null
    specifiers: [
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'a'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'a'
        declaration: no
    ,
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'b'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'b'
        declaration: no
    ,
      type: 'ExportSpecifier'
      local:
        type: 'Identifier'
        name: 'c'
        declaration: no
      exported:
        type: 'Identifier'
        name: 'c'
        declaration: no
    ]

test "AST as expected for ImportDefaultSpecifier node", ->
  testStatement 'import React from "react"',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportDefaultSpecifier'
      local:
        type: 'Identifier'
        name: 'React'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: 'react'

test "AST as expected for ImportNamespaceSpecifier node", ->
  testStatement 'import * as React from "react"',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportNamespaceSpecifier'
      local:
        type: 'Identifier'
        name: 'React'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: 'react'

  testStatement 'import React, * as ReactStar from "react"',
    type: 'ImportDeclaration'
    specifiers: [
      type: 'ImportDefaultSpecifier'
      local:
        type: 'Identifier'
        name: 'React'
        declaration: no
    ,
      type: 'ImportNamespaceSpecifier'
      local:
        type: 'Identifier'
        name: 'ReactStar'
        declaration: no
    ]
    importKind: 'value'
    source:
      type: 'StringLiteral'
      value: 'react'

test "AST as expected for Assign node", ->
  testExpression 'a = b',
    type: 'AssignmentExpression'
    left:
      type: 'Identifier'
      name: 'a'
      declaration: yes
    right:
      type: 'Identifier'
      name: 'b'
      declaration: no
    operator: '='

  testExpression 'a += b',
    type: 'AssignmentExpression'
    left:
      type: 'Identifier'
      name: 'a'
      declaration: no
    right:
      type: 'Identifier'
      name: 'b'
      declaration: no
    operator: '+='

  testExpression '[@a = 2, {b: {c = 3} = {}, d...}, ...e] = f',
    type: 'AssignmentExpression'
    left:
      type: 'ArrayPattern'
      elements: [
        type: 'AssignmentPattern'
        left:
          type: 'MemberExpression'
          object:
            type: 'ThisExpression'
          property:
            name: 'a'
            declaration: no
        right:
          type: 'NumericLiteral'
      ,
        type: 'ObjectPattern'
        properties: [
          type: 'ObjectProperty'
          key:
            name: 'b'
            declaration: no
          value:
            type: 'AssignmentPattern'
            left:
              type: 'ObjectPattern'
              properties: [
                type: 'ObjectProperty'
                key:
                  name: 'c'
                value:
                  type: 'AssignmentPattern'
                  left:
                    name: 'c'
                    declaration: yes
                  right:
                    value: 3
                shorthand: yes
              ]
            right:
              type: 'ObjectExpression'
              properties: []
        ,
          type: 'RestElement'
          argument:
            name: 'd'
            declaration: yes
          postfix: yes
        ]
      ,
        type: 'RestElement'
        argument:
          name: 'e'
          declaration: yes
        postfix: no
      ]
    right:
      name: 'f'

  testExpression '{a: [...b]} = c',
    type: 'AssignmentExpression'
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key:
          name: 'a'
          declaration: no
        value:
          type: 'ArrayPattern'
          elements: [
            type: 'RestElement'
            argument:
              name: 'b'
              declaration: yes
          ]
      ]
    right:
      name: 'c'
      declaration: no

  testExpression '(a = 1; a ?= b)',
    type: 'SequenceExpression'
    expressions: [
      type: 'AssignmentExpression'
    ,
      type: 'AssignmentExpression'
      left:
        type: 'Identifier'
        name: 'a'
        declaration: no
      right:
        type: 'Identifier'
        name: 'b'
        declaration: no
      operator: '?='
    ]

  testExpression '[a..., b] = c',
    type: 'AssignmentExpression'
    left:
      type: 'ArrayPattern'
      elements: [
        type: 'RestElement'
        argument: ID 'a', declaration: yes
        postfix: yes
      ,
        ID 'b'
      ]
    right:
      ID 'c'

  testExpression '[] = c',
    type: 'AssignmentExpression'
    left:
      type: 'ArrayPattern'
      elements: []
    right:
      ID 'c'

  testExpression '{{a...}...} = b',
    type: 'AssignmentExpression'
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'RestElement'
        argument:
          type: 'ObjectPattern'
          properties: [
            type: 'RestElement'
            argument: ID 'a'
          ]
        postfix: yes
      ]
    right: ID 'b'

  testExpression '{a..., b} = c',
    type: 'AssignmentExpression'
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'RestElement'
        argument: ID 'a'
        postfix: yes
      ,
        type: 'ObjectProperty'
      ]
    right: ID 'c'

  testExpression '{a.b...} = c',
    type: 'AssignmentExpression'
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'RestElement'
        argument:
          type: 'MemberExpression'
        postfix: yes
      ]
    right: ID 'c'

  testExpression '{{a}...} = b',
    type: 'AssignmentExpression'
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'RestElement'
        argument:
          type: 'ObjectPattern'
          properties: [
            type: 'ObjectProperty'
            shorthand: yes
          ]
        postfix: yes
      ]
    right: ID 'b'

  testExpression '[u, [v, ...w, x], ...{...y}, z] = a',
    left:
      type: 'ArrayPattern'

  testExpression '{...{a: [...b, c]}} = d',
    left:
      type: 'ObjectPattern'

  testExpression '{"#{a}": b} = c',
    left:
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key:
          type: 'TemplateLiteral'
          expressions: [
            ID 'a'
          ]
        computed: yes
      ]

test "AST as expected for Code node", ->
  testExpression '=>',
    type: 'ArrowFunctionExpression'
    params: []
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null
    hasIndentedBody: no

  testExpression '''
    (a, b = 1) ->
      c
      d()
  ''',
    type: 'FunctionExpression'
    params: [
      type: 'Identifier'
      name: 'a'
      declaration: no
    ,
      type: 'AssignmentPattern'
      left:
        type: 'Identifier'
        name: 'b'
        declaration: no
      right:
        type: 'NumericLiteral'
        value: 1
    ]
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
          name: 'c'
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
          returns: yes
      ]
      directives: []
    generator: no
    async: no
    id: null
    hasIndentedBody: yes

  testExpression '({a}) ->',
    type: 'FunctionExpression'
    params: [
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key: ID 'a', declaration: no
        value: ID 'a', declaration: no
        shorthand: yes
      ]
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '([a]) ->',
    type: 'FunctionExpression'
    params: [
      type: 'ArrayPattern'
      elements: [
        ID 'a', declaration: no
      ]
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '({a = 1} = {}) ->',
    type: 'FunctionExpression'
    params: [
      type: 'AssignmentPattern'
      left:
        type: 'ObjectPattern'
        properties: [
          type: 'ObjectProperty'
          key: ID 'a', declaration: no
          value:
            type: 'AssignmentPattern'
            left: ID 'a', declaration: no
            right: NUMBER(1)
          shorthand: yes
        ]
      right:
        type: 'ObjectExpression'
        properties: []
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '([a = 1] = []) ->',
    type: 'FunctionExpression'
    params: [
      type: 'AssignmentPattern'
      left:
        type: 'ArrayPattern'
        elements: [
          type: 'AssignmentPattern'
          left: ID 'a', declaration: no
          right: NUMBER(1)
        ]
      right:
        type: 'ArrayExpression'
        elements: []
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '() ->',
    type: 'FunctionExpression'
    params: []
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '(@a) ->',
    type: 'FunctionExpression'
    params: [
      type: 'MemberExpression'
      object:
        type: 'ThisExpression'
        shorthand: yes
      property: ID 'a', declaration: no
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '(@a = 1) ->',
    type: 'FunctionExpression'
    params: [
      type: 'AssignmentPattern'
      left:
        type: 'MemberExpression'
      right: NUMBER 1
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '({@a}) ->',
    type: 'FunctionExpression'
    params: [
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key:
          type: 'MemberExpression'
        value:
          type: 'MemberExpression'
        shorthand: yes
        computed: no
      ]
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '({[a]}) ->',
    type: 'FunctionExpression'
    params: [
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key:   ID 'a', declaration: no
        value: ID 'a', declaration: no
        shorthand: yes
        computed: yes
      ]
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '(...a) ->',
    type: 'FunctionExpression'
    params: [
      type: 'RestElement'
      argument: ID 'a', declaration: no
      postfix: no
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '(a...) ->',
    type: 'FunctionExpression'
    params: [
      type: 'RestElement'
      argument: ID 'a'
      postfix: yes
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '(..., a) ->',
    type: 'FunctionExpression'
    params: [
      type: 'RestElement'
      argument: null
    ,
      ID 'a'
    ]
    body: EMPTY_BLOCK
    generator: no
    async: no
    id: null

  testExpression '-> a',
    type: 'FunctionExpression'
    params: []
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'a', returns: yes
      ]
    generator: no
    async: no
    id: null
    hasIndentedBody: no

  testExpression '-> await 3',
    type: 'FunctionExpression'
    params: []
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AwaitExpression'
          argument: NUMBER 3
          returns: yes
      ]
    generator: no
    async: yes
    id: null

  testExpression '-> yield 4',
    type: 'FunctionExpression'
    params: []
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'YieldExpression'
          argument: NUMBER 4
          delegate: no
      ]
    generator: yes
    async: no
    id: null

  testExpression '-> yield',
    type: 'FunctionExpression'
    params: []
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'YieldExpression'
          argument: null
          delegate: no
      ]
    generator: yes
    async: no
    id: null

  testExpression '(a) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]

  testExpression '(...a) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]

  testExpression '({a}) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]

  testExpression '([a]) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]

  testExpression '(a = 1) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]
    generator: no
    async: no
    id: null

  testExpression '({a} = 1) -> a = 1',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
          left:
            ID 'a', declaration: no
      ]
    generator: no
    async: no
    id: null

test "AST as expected for Splat node", ->
  testExpression '[a...]',
    type: 'ArrayExpression'
    elements: [
      type: 'SpreadElement'
      argument:
        type: 'Identifier'
        name: 'a'
        declaration: no
      postfix: yes
    ]

  testExpression '[b, ...c]',
    type: 'ArrayExpression'
    elements: [
      name: 'b'
      declaration: no
    ,
      type: 'SpreadElement'
      argument:
        type: 'Identifier'
        name: 'c'
        declaration: no
      postfix: no
    ]

test "AST as expected for Expansion node", ->
  testExpression '(..., b) ->',
    type: 'FunctionExpression'
    params: [
      type: 'RestElement'
      argument: null
    ,
      ID 'b'
    ]

  testExpression '[..., b] = c',
    type: 'AssignmentExpression'
    left:
      type: 'ArrayPattern'
      elements: [
        type: 'RestElement'
        argument: null
      ,
        type: 'Identifier'
      ]

test "AST as expected for Elision node", ->
  testExpression '[,,,a,,,b]',
    type: 'ArrayExpression'
    elements: [
      null, null, null
      name: 'a'
      null, null
      name: 'b'
    ]

  testExpression '[,,,a,,,b] = "asdfqwer"',
    type: 'AssignmentExpression'
    left:
      type: 'ArrayPattern'
      elements: [
        null, null, null
      ,
        type: 'Identifier'
        name: 'a'
      ,
        null, null
      ,
        type: 'Identifier'
        name: 'b'
      ]
    right:
      type: 'StringLiteral'
      value: 'asdfqwer'

test "AST as expected for While node", ->
  testStatement 'loop 1',
    type: 'WhileStatement'
    test:
      type: 'BooleanLiteral'
      value: true
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: NUMBER 1
      ]
    guard: null
    inverted: no
    postfix: no
    loop: yes

  testStatement 'while 1 < 2 then',
    type: 'WhileStatement'
    test:
      type: 'BinaryExpression'
    body:
      type: 'BlockStatement'
      body: []
    guard: null
    inverted: no
    postfix: no
    loop: no

  testStatement 'while 1 < 2 then fn()',
    type: 'WhileStatement'
    test:
      type: 'BinaryExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ]
    guard: null
    inverted: no
    postfix: no
    loop: no

  testStatement '''
    x() until y
  ''',
    type: 'WhileStatement'
    test: ID 'y'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
          returns: undefined
      ]
    guard: null
    inverted: yes
    postfix: yes
    loop: no

  testStatement '''
    until x when y
      z++
  ''',
    type: 'WhileStatement'
    test: ID 'x'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'UpdateExpression'
      ]
    guard: ID 'y'
    inverted: yes
    postfix: no
    loop: no

  testStatement '''
    x while y when z
  ''',
    type: 'WhileStatement'
    test: ID 'y'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'x'
      ]
    guard: ID 'z'
    inverted: no
    postfix: yes
    loop: no

  testStatement '''
    loop
      a()
      b++
  ''',
    type: 'WhileStatement'
    test:
      type: 'BooleanLiteral'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'UpdateExpression'
      ]
    guard: null
    inverted: no
    postfix: no
    loop: yes

  testExpression '''
    x = (z() while y)
  ''',
    type: 'AssignmentExpression'
    right:
      type: 'WhileStatement'
      body:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression:
            type: 'CallExpression'
            returns: yes
        ]

test "AST as expected for Op node", ->
  testExpression 'a <= 2',
    type: 'BinaryExpression'
    operator: '<='
    left:
      type: 'Identifier'
      name: 'a'
    right:
      type: 'NumericLiteral'
      value: 2

  testExpression 'a is 2',
    type: 'BinaryExpression'
    operator: 'is'
    left:
      type: 'Identifier'
      name: 'a'
    right:
      type: 'NumericLiteral'
      value: 2

  testExpression 'a // 2',
    type: 'BinaryExpression'
    operator: '//'
    left:
      type: 'Identifier'
      name: 'a'
    right:
      type: 'NumericLiteral'
      value: 2

  testExpression 'a << 2',
    type: 'BinaryExpression'
    operator: '<<'
    left:
      type: 'Identifier'
      name: 'a'
    right:
      type: 'NumericLiteral'
      value: 2

  testExpression 'typeof x',
    type: 'UnaryExpression'
    operator: 'typeof'
    prefix: yes
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression 'delete x.y',
    type: 'UnaryExpression'
    operator: 'delete'
    prefix: yes
    argument:
      type: 'MemberExpression'

  testExpression 'do x',
    type: 'UnaryExpression'
    operator: 'do'
    prefix: yes
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression 'do ->',
    type: 'UnaryExpression'
    operator: 'do'
    prefix: yes
    argument:
      type: 'FunctionExpression'

  testExpression '!x',
    type: 'UnaryExpression'
    operator: '!'
    prefix: yes
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression 'not x',
    type: 'UnaryExpression'
    operator: 'not'
    prefix: yes
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression '--x',
    type: 'UpdateExpression'
    operator: '--'
    prefix: yes
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression 'x++',
    type: 'UpdateExpression'
    operator: '++'
    prefix: no
    argument:
      type: 'Identifier'
      name: 'x'

  testExpression 'x && y',
    type: 'LogicalExpression'
    operator: '&&'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'Identifier'
      name: 'y'

  testExpression 'x or y',
    type: 'LogicalExpression'
    operator: 'or'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'Identifier'
      name: 'y'

  testExpression 'x ? y',
    type: 'LogicalExpression'
    operator: '?'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'Identifier'
      name: 'y'

  testExpression 'x in y',
    type: 'BinaryExpression'
    operator: 'in'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'Identifier'
      name: 'y'

  testExpression 'x not in y',
    type: 'BinaryExpression'
    operator: 'not in'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'Identifier'
      name: 'y'

  testExpression 'x + y * z',
    type: 'BinaryExpression'
    operator: '+'
    left:
      type: 'Identifier'
      name: 'x'
    right:
      type: 'BinaryExpression'
      operator: '*'
      left:
        type: 'Identifier'
        name: 'y'
      right:
        type: 'Identifier'
        name: 'z'

  testExpression '(x + y) * z',
    type: 'BinaryExpression'
    operator: '*'
    left:
      type: 'BinaryExpression'
      operator: '+'
      left:
        type: 'Identifier'
        name: 'x'
      right:
        type: 'Identifier'
        name: 'y'
    right:
      type: 'Identifier'
      name: 'z'

test "AST as expected for Try node", ->
  testStatement 'try cappuccino',
    type: 'TryStatement'
    block:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
          name: 'cappuccino'
      ]
    handler: null
    finalizer: null

  testStatement '''
    try
      x = 1
      y()
    catch e
      d()
    finally
      f + g
  ''',
    type: 'TryStatement'
    block:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'AssignmentExpression'
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ]
    handler:
      type: 'CatchClause'
      param:
        type: 'Identifier'
        name: 'e'
        declaration: yes
      body:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression:
            type: 'CallExpression'
        ]
    finalizer:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'BinaryExpression'
      ]

  testStatement '''
    try
    catch
    finally
  ''',
    type: 'TryStatement'
    block:
      type: 'BlockStatement'
      body: []
    handler:
      type: 'CatchClause'
      param: null
      body:
        type: 'BlockStatement'
        body: []
    finalizer:
      type: 'BlockStatement'
      body: []

  testStatement '''
    try
    catch {e}
      f
  ''',
    type: 'TryStatement'
    block:
      type: 'BlockStatement'
      body: []
    handler:
      type: 'CatchClause'
      param:
        type: 'ObjectPattern'
        properties: [
          type: 'ObjectProperty'
          key: ID 'e', declaration: no
          value: ID 'e', declaration: yes
        ]
      body:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
        ]
    finalizer: null

test "AST as expected for Throw node", ->
  testStatement 'throw new BallError "catch"',
    type: 'ThrowStatement'
    argument:
      type: 'NewExpression'

test "AST as expected for Existence node", ->
  testExpression 'Ghosts?',
    type: 'UnaryExpression',
    argument:
      name: 'Ghosts'
    operator: '?'
    prefix: no

test "AST as expected for Parens node", ->
  testExpression '(hmmmmm)',
    type: 'Identifier'
    name: 'hmmmmm'

  testExpression '(a + b) / c',
    type: 'BinaryExpression'
    operator: '/'
    left:
      type: 'BinaryExpression'
      operator: '+'
      left: ID 'a'
      right: ID 'b'
    right: ID 'c'

  testExpression '(((1)))',
    type: 'NumericLiteral'
    value: 1

test "AST as expected for StringWithInterpolations node", ->
  testExpression '"a#{b}c"',
    type: 'TemplateLiteral'
    expressions: [
      ID 'b'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: 'a'
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: 'c'
      tail: yes
    ]
    quote: '"'

  testExpression '"""a#{b}c"""',
    type: 'TemplateLiteral'
    expressions: [
      ID 'b'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: 'a'
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: 'c'
      tail: yes
    ]
    quote: '"""'

  testExpression '"#{b}"',
    type: 'TemplateLiteral'
    expressions: [
      ID 'b'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: ''
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: ''
      tail: yes
    ]
    quote: '"'

  testExpression '''
    " a
      #{b}
      c
    "
  ''',
    type: 'TemplateLiteral'
    expressions: [
      ID 'b'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: ' a\n  '
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: '\n  c\n'
      tail: yes
    ]
    quote: '"'

  testExpression '''
    """
      a
        b#{
        c
      }d
    """
  ''',
    type: 'TemplateLiteral'
    expressions: [
      ID 'c'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: '\n  a\n    b'
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: 'd\n'
      tail: yes
    ]
    quote: '"""'

  # empty interpolation
  testExpression '"#{}"',
    type: 'TemplateLiteral'
    expressions: [
      type: 'EmptyInterpolation'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: ''
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: ''
      tail: yes
    ]
    quote: '"'

  testExpression '''
    "#{
      # comment
     }"
    ''',
    type: 'TemplateLiteral'
    expressions: [
      type: 'EmptyInterpolation'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: ''
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: ''
      tail: yes
    ]
    quote: '"'

  testExpression '"#{ ### here ### }"',
    type: 'TemplateLiteral'
    expressions: [
      type: 'EmptyInterpolation'
    ]
    quasis: [
      type: 'TemplateElement'
      value:
        raw: ''
      tail: no
    ,
      type: 'TemplateElement'
      value:
        raw: ''
      tail: yes
    ]
    quote: '"'

  testExpression '''
    a "#{
      b
      c
    }"
  ''',
    type: 'CallExpression'
    arguments: [
      type: 'TemplateLiteral'
      expressions: [
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
        ,
          type: 'ExpressionStatement'
          expression:
            returns: yes
        ]
      ]
    ]

test "AST as expected for For node", ->
  testStatement 'for x, i in arr when x? then return',
    type: 'For'
    name: ID 'x', declaration: yes
    index: ID 'i', declaration: yes
    guard:
      type: 'UnaryExpression'
    source: ID 'arr', declaration: no
    body:
      type: 'BlockStatement'
      body: [
        type: 'ReturnStatement'
      ]
    style: 'in'
    own: no
    postfix: no
    await: no
    step: null

  testStatement 'for k, v of obj then return',
    type: 'For'
    name: ID 'v', declaration: yes
    index: ID 'k', declaration: yes
    guard: null
    source: ID 'obj', declaration: no
    body:
      type: 'BlockStatement'
      body: [
        type: 'ReturnStatement'
      ]
    style: 'of'
    own: no
    postfix: no
    await: no
    step: null

  testStatement 'for x from iterable then',
    type: 'For'
    name: ID 'x', declaration: yes
    index: null
    guard: null
    body: EMPTY_BLOCK
    source: ID 'iterable', declaration: no
    style: 'from'
    own: no
    postfix: no
    await: no
    step: null

  testStatement 'for i in [0...42] by step when not (i % 2) then',
    type: 'For'
    name: ID 'i', declaration: yes
    index: null
    body: EMPTY_BLOCK
    source:
      type: 'Range'
    guard:
      type: 'UnaryExpression'
    step: ID 'step', declaration: no
    style: 'in'
    own: no
    postfix: no
    await: no

  testExpression 'a = (x for x in y)',
    type: 'AssignmentExpression'
    right:
      type: 'For'
      name: ID 'x', declaration: yes
      index: null
      body:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression: ID 'x', declaration: no, returns: yes
        ]
      source: ID 'y', declaration: no
      guard: null
      step: null
      style: 'in'
      own: no
      postfix: yes
      await: no

  testStatement 'x for [0...1]',
    type: 'For'
    name: null
    index: null
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'x', declaration: no, returns: undefined
      ]
    source:
      type: 'Range'
    guard: null
    step: null
    style: 'range'
    own: no
    postfix: yes
    await: no

  testStatement '''
    for own x, y of z
      c()
      d
  ''',
    type: 'For'
    name: ID 'y', declaration: yes
    index: ID 'x', declaration: yes
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
          returns: undefined
      ,
        type: 'ExpressionStatement'
        expression: ID 'd', declaration: no, returns: undefined
      ]
    source: ID 'z', declaration: no
    guard: null
    step: null
    style: 'of'
    own: yes
    postfix: no
    await: no

  testExpression '''
    ->
      for await x from y
        z
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'For'
        name: ID 'x', declaration: yes
        index: null
        body:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression: ID 'z', declaration: no, returns: yes
          ]
        source: ID 'y', declaration: no
        guard: null
        step: null
        style: 'from'
        own: no
        postfix: no
        await: yes
      ]

  testStatement '''
    for {x} in y
      z
  ''',
    type: 'For'
    name:
      type: 'ObjectPattern'
      properties: [
        type: 'ObjectProperty'
        key: ID 'x', declaration: no
        value: ID 'x', declaration: yes
        shorthand: yes
        computed: no
      ]
    index: null
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'z'
      ]
    source: ID 'y'
    guard: null
    step: null
    style: 'in'
    postfix: no
    await: no

  testStatement '''
    for [x] in y
      z
  ''',
    type: 'For'
    name:
      type: 'ArrayPattern'
      elements: [
        ID 'x', declaration: yes
      ]
    index: null
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'z'
      ]
    source: ID 'y'
    guard: null
    step: null
    style: 'in'
    postfix: no
    await: no

  testStatement '''
    for [x..., y] in z
      y()
  ''',
    type: 'For'
    name:
      type: 'ArrayPattern'

test "AST as expected for Switch node", ->
  testStatement '''
    switch x
      when a then a
      when b, c then c
      else 42
  ''',
    type: 'SwitchStatement'
    discriminant:
      type: 'Identifier'
      name: 'x'
    cases: [
      type: 'SwitchCase'
      test:
        type: 'Identifier'
        name: 'a'
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
          name: 'a'
      ]
      trailing: yes
    ,
      type: 'SwitchCase'
      test:
        type: 'Identifier'
        name: 'b'
      consequent: []
      trailing: no
    ,
      type: 'SwitchCase'
      test:
        type: 'Identifier'
        name: 'c'
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
          name: 'c'
      ]
      trailing: yes
    ,
      type: 'SwitchCase'
      test: null
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'NumericLiteral'
          value: 42
      ]
    ]

  testStatement '''
    switch
      when some(condition)
        doSomething()
        andThenSomethingElse
  ''',
    type: 'SwitchStatement'
    discriminant: null
    cases: [
      type: 'SwitchCase'
      test:
        type: 'CallExpression'
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
      ]
      trailing: yes
    ]

  testStatement '''
    switch a
      when 1, 2, 3, 4
        b
      else
        c
        d
  ''',
    type: 'SwitchStatement'
    discriminant:
      type: 'Identifier'
    cases: [
      type: 'SwitchCase'
      test:
        type: 'NumericLiteral'
        value: 1
      consequent: []
      trailing: no
    ,
      type: 'SwitchCase'
      test:
        type: 'NumericLiteral'
        value: 2
      consequent: []
      trailing: no
    ,
      type: 'SwitchCase'
      test:
        type: 'NumericLiteral'
        value: 3
      consequent: []
      trailing: no
    ,
      type: 'SwitchCase'
      test:
        type: 'NumericLiteral'
        value: 4
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
      ]
      trailing: yes
    ,
      type: 'SwitchCase'
      test: null
      consequent: [
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
      ,
        type: 'ExpressionStatement'
        expression:
          type: 'Identifier'
      ]
    ]

test "AST as expected for If node", ->
  testStatement 'if maybe then yes',
    type: 'IfStatement'
    test: ID 'maybe'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'BooleanLiteral'
      ]
    alternate: null
    postfix: no
    inverted: no

  testStatement 'yes if maybe',
    type: 'IfStatement'
    test: ID 'maybe'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'BooleanLiteral'
      ]
    alternate: null
    postfix: yes
    inverted: no

  testStatement 'unless x then x else if y then y else z',
    type: 'IfStatement'
    test: ID 'x'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'x'
      ]
    alternate:
      type: 'IfStatement'
      test: ID 'y'
      consequent:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression: ID 'y'
        ]
      alternate:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression: ID 'z'
        ]
      postfix: no
      inverted: no
    postfix: no
    inverted: yes

  testStatement '''
    if a
      b
    else
      if c
        d
  ''',
    type: 'IfStatement'
    test: ID 'a'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'b'
      ]
    alternate:
      type: 'BlockStatement'
      body: [
        type: 'IfStatement'
        test: ID 'c'
        consequent:
          type: 'BlockStatement'
          body: [
            type: 'ExpressionStatement'
            expression: ID 'd'
          ]
        alternate: null
        postfix: no
        inverted: no
      ]
    postfix: no
    inverted: no

  testExpression '''
    a =
      if b then c else if d then e
  ''',
    type: 'AssignmentExpression'
    right:
      type: 'ConditionalExpression'
      test: ID 'b'
      consequent: ID 'c'
      alternate:
        type: 'ConditionalExpression'
        test: ID 'd'
        consequent: ID 'e'
        alternate: null
        postfix: no
        inverted: no
      postfix: no
      inverted: no

  testExpression '''
    f(
      if b
        c
        d
    )
  ''',
    type: 'CallExpression'
    arguments: [
      type: 'ConditionalExpression'
      test: ID 'b'
      consequent:
        type: 'BlockStatement'
        body: [
          type: 'ExpressionStatement'
          expression:
            ID 'c'
        ,
          type: 'ExpressionStatement'
          expression:
            ID 'd'
        ]
      alternate: null
      postfix: no
      inverted: no
    ]

  testStatement 'a unless b',
    type: 'IfStatement'
    test: ID 'b'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'a'
      ]
    alternate: null
    postfix: yes
    inverted: yes

  testExpression '''
    f(
      if b
        c
      else
        d
    )
  ''',
      type: 'CallExpression'
      arguments: [
        type: 'ConditionalExpression'
        test: ID 'b'
        consequent: ID 'c'
        alternate: ID 'd'
        postfix: no
        inverted: no
      ]

test "AST as expected for `new.target` MetaProperty node", ->
  testExpression '''
    -> new.target
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'MetaProperty'
          meta: ID 'new'
          property: ID 'target'
      ]

  testExpression '''
    -> new.target.name
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'MemberExpression'
          object:
            type: 'MetaProperty'
            meta: ID 'new'
            property: ID 'target'
          property: ID 'name'
          computed: no
      ]

test "AST as expected for `import.meta` MetaProperty node", ->
  testExpression '''
    import.meta
  ''',
    type: 'MetaProperty'
    meta: ID 'import'
    property: ID 'meta'

  testExpression '''
    import.meta.name
  ''',
    type: 'MemberExpression'
    object:
      type: 'MetaProperty'
      meta: ID 'import'
      property: ID 'meta'
    property: ID 'name'
    computed: no

test "AST as expected for dynamic import", ->
  testExpression '''
    import('a')
  ''',
    type: 'CallExpression'
    callee:
      type: 'Import'
    arguments: [STRING 'a']

test "AST as expected for RegexLiteral node", ->
  testExpression '/a/ig',
    type: 'RegExpLiteral'
    pattern: 'a'
    originalPattern: 'a'
    flags: 'ig'
    delimiter: '/'
    value: undefined
    extra:
      raw: "/a/ig"
      originalRaw: "/a/ig"
      rawValue: undefined

  testExpression '''
    ///
      a
    ///i
  ''',
    type: 'RegExpLiteral'
    pattern: 'a'
    originalPattern: '\n  a\n'
    flags: 'i'
    delimiter: '///'
    value: undefined
    extra:
      raw: "/a/i"
      originalRaw: "///\n  a\n///i"
      rawValue: undefined

  testExpression '/a\\w\\u1111\\u{11111}/',
    type: 'RegExpLiteral'
    pattern: 'a\\w\\u1111\\ud804\\udd11'
    originalPattern: 'a\\w\\u1111\\u{11111}'
    flags: ''
    delimiter: '/'
    value: undefined
    extra:
      raw: "/a\\w\\u1111\\ud804\\udd11/"
      originalRaw: "/a\\w\\u1111\\u{11111}/"
      rawValue: undefined

  testExpression '''
    ///
      a
      \\w\\u1111\\u{11111}
    ///
  ''',
    type: 'RegExpLiteral'
    pattern: 'a\\w\\u1111\\ud804\\udd11'
    originalPattern: '\n  a\n  \\w\\u1111\\u{11111}\n'
    flags: ''
    delimiter: '///'
    value: undefined
    extra:
      raw: "/a\\w\\u1111\\ud804\\udd11/"
      originalRaw: "///\n  a\n  \\w\\u1111\\u{11111}\n///"
      rawValue: undefined

  testExpression '''
    ///
      /
      (.+)
      /
    ///
  ''',
    type: 'RegExpLiteral'
    pattern: '\\/(.+)\\/'
    originalPattern: '\n  /\n  (.+)\n  /\n'
    flags: ''
    delimiter: '///'
    value: undefined
    extra:
      raw: "/\\/(.+)\\//"
      originalRaw: "///\n  /\n  (.+)\n  /\n///"
      rawValue: undefined

  testExpression '''
    ///
      a # first
      b ### second ###
    ///
  ''',
    type: 'RegExpLiteral'
    pattern: 'ab'
    originalPattern: '\n  a # first\n  b ### second ###\n'
    comments: [
      type: 'CommentLine'
      value: ' first'
    ,
      type: 'CommentBlock'
      value: ' second '
    ]

test "AST as expected for directives", ->
  deepStrictIncludeExpectedProperties CoffeeScript.compile('''
    'directive 1'
    'use strict'
    f()
  ''', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ]
      directives: [
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'directive 1'
          extra:
            raw: "'directive 1'"
      ,
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'use strict'
          extra:
            raw: "'use strict'"
      ]

  testExpression '''
    ->
      'use strict'
      f()
      'not a directive'
      g
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'CallExpression'
      ,
        type: 'ExpressionStatement'
        expression: STRING 'not a directive'
      ,
        type: 'ExpressionStatement'
        expression: ID 'g'
      ]
      directives: [
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'use strict'
          extra:
            raw: "'use strict'"
      ]

  testExpression '''
    ->
      "not a directive because it's implicitly returned"
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: STRING "not a directive because it's implicitly returned"
      ]
      directives: []

  deepStrictIncludeExpectedProperties CoffeeScript.compile('''
    'use strict'
  ''', ast: yes),
    type: 'File'
    program:
      type: 'Program'
      body: []
      directives: [
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'use strict'
          extra:
            raw: "'use strict'"
      ]

  testStatement '''
    class A
      'classes can have directives too'
      a: ->
  ''',
    type: 'ClassDeclaration'
    body:
      type: 'ClassBody'
      body: [
        type: 'ClassMethod'
      ]
      directives: [
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'classes can have directives too'
      ]

  testStatement '''
    if a
      "but other blocks can't"
      b
  ''',
    type: 'IfStatement'
    consequent:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: STRING "but other blocks can't"
      ,
        type: 'ExpressionStatement'
        expression: ID 'b'
      ]
      directives: []

  testExpression '''
    ->
      """not a directive"""
      b
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression:
          type: 'TemplateLiteral'
      ,
        type: 'ExpressionStatement'
        expression: ID 'b'
      ]
      directives: []

  testExpression '''
    ->
      # leading comment
      'use strict'
      b
  ''',
    type: 'FunctionExpression'
    body:
      type: 'BlockStatement'
      body: [
        type: 'ExpressionStatement'
        expression: ID 'b'
      ]
      directives: [
        type: 'Directive'
        value:
          type: 'DirectiveLiteral'
          value: 'use strict'
          extra:
            raw: "'use strict'"
      ]

test "AST as expected for comments", ->
  testComments '''
    a # simple line comment
  ''', [
    type: 'CommentLine'
    value: ' simple line comment'
  ]

  testComments '''
    a ### simple here comment ###
  ''', [
    type: 'CommentBlock'
    value: ' simple here comment '
  ]

  testComments '''
    # just a line comment
  ''', [
    type: 'CommentLine'
    value: ' just a line comment'
  ]

  testComments '''
    ### just a here comment ###
  ''', [
    type: 'CommentBlock'
    value: ' just a here comment '
  ]

  testComments '''
    "#{
      # empty interpolation line comment
     }"
  ''', [
    type: 'CommentLine'
    value: ' empty interpolation line comment'
  ]

  testComments '''
    "#{
      ### empty interpolation block comment ###
     }"
  ''', [
    type: 'CommentBlock'
    value: ' empty interpolation block comment '
  ]

  testComments '''
    # multiple line comments
    # on consecutive lines
  ''', [
    type: 'CommentLine'
    value: ' multiple line comments'
  ,
    type: 'CommentLine'
    value: ' on consecutive lines'
  ]

  testComments '''
    # multiple line comments

    # with blank line
  ''', [
    type: 'CommentLine'
    value: ' multiple line comments'
  ,
    type: 'CommentLine'
    value: ' with blank line'
  ]

  testComments '''
    #no whitespace line comment
  ''', [
    type: 'CommentLine'
    value: 'no whitespace line comment'
  ]

  testComments '''
    ###no whitespace here comment###
  ''', [
    type: 'CommentBlock'
    value: 'no whitespace here comment'
  ]

  testComments '''
    ###
    # multiline
    # here comment
    ###
  ''', [
    type: 'CommentBlock'
    value: '\n# multiline\n# here comment\n'
  ]

  testComments '''
    if b
      ###
      # multiline
      # indented here comment
      ###
      c
  ''', [
    type: 'CommentBlock'
    value: '\n  # multiline\n  # indented here comment\n  '
  ]

  testComments '''
    if foo
      ;
      ### empty ###
  ''', [
    type: 'CommentBlock'
    value: ' empty '
  ]

test "AST as expected for chained comparisons", ->
  testExpression '''
    a < b < c
  ''',
    type: 'ChainedComparison'
    operands: [
      ID 'a'
      ID 'b'
      ID 'c'
    ]
    operators: [
      '<'
      '<'
    ]

  testExpression '''
    a isnt b is c isnt d
  ''',
    type: 'ChainedComparison'
    operands: [
      ID 'a'
      ID 'b'
      ID 'c'
      ID 'd'
    ]
    operators: [
      'isnt'
      'is'
      'isnt'
    ]

  testExpression '''
    a >= b < c
  ''',
    type: 'ChainedComparison'
    operands: [
      ID 'a'
      ID 'b'
      ID 'c'
    ]
    operators: [
      '>='
      '<'
    ]

test "AST as expected for Sequence", ->
  testExpression '''
    (a; b)
  ''',
    type: 'SequenceExpression'
    expressions: [
      ID 'a'
      ID 'b'
    ]

  testExpression '''
    (a; b)""
  ''',
    type: 'TaggedTemplateExpression'
    tag:
      type: 'SequenceExpression'
      expressions: [
        ID 'a'
        ID 'b'
      ]

</script>
<script type="text/x-coffeescript" class="test" id="abstract_syntax_tree_location_data">
# Astract Syntax Tree location data
# ---------------------------------

testAstLocationData = (code, expected) ->
  testAstNodeLocationData getAstExpressionOrStatement(code), expected

testAstRootLocationData = (code, expected) ->
  testAstNodeLocationData getAstRoot(code), expected

testAstNodeLocationData = (node, expected, path = '') ->
  extendPath = (additionalPath) ->
    return additionalPath unless path
    "#{path}.#{additionalPath}"
  ok node?, "Missing expected node at '#{path}'"
  testSingleNodeLocationData node, expected, path if expected.range?
  for own key, expectedChild of expected when key not in ['start', 'end', 'range', 'loc']
    if Array.isArray expectedChild
      ok Array.isArray(node[key]), "Missing expected array at '#{extendPath key}'"
      for expectedItem, index in expectedChild when expectedItem?
        testAstNodeLocationData node[key][index], expectedItem, extendPath "#{key}[#{index}]"
    else if typeof expectedChild is 'object'
      testAstNodeLocationData node[key], expectedChild, extendPath(key)

testSingleNodeLocationData = (node, expected, path = '') ->
  # Even though it’s not part of the location data, check the type to ensure
  # that we’re testing the node we think we are.
  if expected.type?
    eq node.type, expected.type, \
      "Expected AST node type #{reset}#{node.type}#{red} to equal #{reset}#{expected.type}#{red}"

  eq node.start, expected.start, \
    "Expected #{path}.start: #{reset}#{node.start}#{red} to equal #{reset}#{expected.start}#{red}"
  eq node.end, expected.end, \
    "Expected #{path}.end: #{reset}#{node.end}#{red} to equal #{reset}#{expected.end}#{red}"
  arrayEq node.range, expected.range, \
    "Expected #{path}.range: #{reset}#{JSON.stringify node.range}#{red} to equal #{reset}#{JSON.stringify expected.range}#{red}"
  eq node.loc.start.line, expected.loc.start.line, \
    "Expected #{path}.loc.start.line: #{reset}#{node.loc.start.line}#{red} to equal #{reset}#{expected.loc.start.line}#{red}"
  eq node.loc.start.column, expected.loc.start.column, \
    "Expected #{path}.loc.start.column: #{reset}#{node.loc.start.column}#{red} to equal #{reset}#{expected.loc.start.column}#{red}"
  eq node.loc.end.line, expected.loc.end.line, \
    "Expected #{path}.loc.end.line: #{reset}#{node.loc.end.line}#{red} to equal #{reset}#{expected.loc.end.line}#{red}"
  eq node.loc.end.column, expected.loc.end.column, \
    "Expected #{path}.loc.end.column: #{reset}#{node.loc.end.column}#{red} to equal #{reset}#{expected.loc.end.column}#{red}"

testAstCommentsLocationData = (code, expected) ->
  testAstNodeLocationData getAstRoot(code).comments, expected

if require?
  {mergeAstLocationData, mergeLocationData} = require './../lib/coffeescript/nodes'

  test "the `mergeAstLocationData` helper accepts `justLeading` and `justEnding` options", ->
    first =
      range: [4, 5]
      start: 4
      end: 5
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 5
    second =
      range: [1, 10]
      start: 1
      end: 10
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 2
          column: 2
    testSingleNodeLocationData mergeAstLocationData(first, second), second
    testSingleNodeLocationData mergeAstLocationData(first, second, justLeading: yes),
      range: [1, 5]
      start: 1
      end: 5
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 5
    testSingleNodeLocationData mergeAstLocationData(first, second, justEnding: yes),
      range: [4, 10]
      start: 4
      end: 10
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 2
          column: 2

  test "the `mergeLocationData` helper accepts `justLeading` and `justEnding` options", ->
    testLocationData = (node, expected) ->
      arrayEq node.range, expected.range
      for field in ['first_line', 'first_column', 'last_line', 'last_column']
        eq node[field], expected[field]

    first =
      range: [4, 5]
      first_line: 0
      first_column: 4
      last_line: 0
      last_column: 4
    second =
      range: [1, 10]
      first_line: 0
      first_column: 1
      last_line: 1
      last_column: 2

    testLocationData mergeLocationData(first, second), second
    testLocationData mergeLocationData(first, second, justLeading: yes),
      range: [1, 5]
      first_line: 0
      first_column: 1
      last_line: 0
      last_column: 4
    testLocationData mergeLocationData(first, second, justEnding: yes),
      range: [4, 10]
      first_line: 0
      first_column: 4
      last_line: 1
      last_column: 2

test "AST location data as expected for NumberLiteral node", ->
  testAstLocationData '42',
    type: 'NumericLiteral'
    start: 0
    end: 2
    range: [0, 2]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 2

test "AST location data as expected for InfinityLiteral node", ->
  testAstLocationData 'Infinity',
    type: 'Identifier'
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 8

  testAstLocationData '2e308',
    type: 'NumericLiteral'
    start: 0
    end: 5
    range: [0, 5]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 5

test "AST location data as expected for NaNLiteral node", ->
  testAstLocationData 'NaN',
    type: 'Identifier'
    start: 0
    end: 3
    range: [0, 3]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 3

test "AST location data as expected for IdentifierLiteral node", ->
  testAstLocationData 'id',
    type: 'Identifier'
    start: 0
    end: 2
    range: [0, 2]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 2

test "AST location data as expected for StatementLiteral node", ->
  testAstLocationData 'break',
    type: 'BreakStatement'
    start: 0
    end: 5
    range: [0, 5]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 5

  testAstLocationData 'continue',
    type: 'ContinueStatement'
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 8

  testAstLocationData 'debugger',
    type: 'DebuggerStatement'
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 8

test "AST location data as expected for ThisLiteral node", ->
  testAstLocationData 'this',
    type: 'ThisExpression'
    start: 0
    end: 4
    range: [0, 4]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 4

test "AST location data as expected for UndefinedLiteral node", ->
  testAstLocationData 'undefined',
    type: 'Identifier'
    start: 0
    end: 9
    range: [0, 9]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 9

test "AST location data as expected for NullLiteral node", ->
  testAstLocationData 'null',
    type: 'NullLiteral'
    start: 0
    end: 4
    range: [0, 4]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 4

test "AST location data as expected for BooleanLiteral node", ->
  testAstLocationData 'true',
    type: 'BooleanLiteral'
    start: 0
    end: 4
    range: [0, 4]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 4

test "AST location data as expected for Access node", ->
  testAstLocationData 'obj.prop',
    type: 'MemberExpression'
    object:
      start: 0
      end: 3
      range: [0, 3]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 3
    property:
      start: 4
      end: 8
      range: [4, 8]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 8
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 8

  testAstLocationData 'a::b',
    type: 'MemberExpression'
    object:
      object:
        start: 0
        end: 1
        range: [0, 1]
        loc:
          start:
            line: 1
            column: 0
          end:
            line: 1
            column: 1
      property:
        start: 1
        end: 3
        range: [1, 3]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 3
    property:
      start: 3
      end: 4
      range: [3, 4]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 1
          column: 4
    start: 0
    end: 4
    range: [0, 4]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 4

  testAstLocationData '''
    (
      obj
    ).prop
  ''',
    type: 'MemberExpression'
    object:
      start: 4
      end: 7
      range: [4, 7]
      loc:
        start:
          line: 2
          column: 2
        end:
          line: 2
          column: 5
    property:
      start: 10
      end: 14
      range: [10, 14]
      loc:
        start:
          line: 3
          column: 2
        end:
          line: 3
          column: 6
    start: 0
    end: 14
    range: [0, 14]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 6

test "AST location data as expected for Index node", ->
  testAstLocationData 'a[b]',
    type: 'MemberExpression'
    object:
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    property:
      start: 2
      end: 3
      range: [2, 3]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 3
    start: 0
    end: 4
    range: [0, 4]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 4

  testAstLocationData 'a?[b][3]',
    type: 'OptionalMemberExpression'
    object:
      object:
        start: 0
        end: 1
        range: [0, 1]
        loc:
          start:
            line: 1
            column: 0
          end:
            line: 1
            column: 1
      property:
        start: 3
        end: 4
        range: [3, 4]
        loc:
          start:
            line: 1
            column: 3
          end:
            line: 1
            column: 4
      start: 0
      end: 5
      range: [0, 5]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 5
    property:
      start: 6
      end: 7
      range: [6, 7]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 7
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 8

test "AST location data as expected for Parens node", ->
  testAstLocationData '(hmmmmm)',
    type: 'Identifier'
    start: 1
    end: 7
    range: [1, 7]
    loc:
      start:
        line: 1
        column: 1
      end:
        line: 1
        column: 7

  testAstLocationData '(((1)))',
    type: 'NumericLiteral'
    start: 3
    end: 4
    range: [3, 4]
    loc:
      start:
        line: 1
        column: 3
      end:
        line: 1
        column: 4

test "AST location data as expected for Op node", ->
  testAstLocationData '1 <= 2',
    type: 'BinaryExpression'
    left:
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    right:
      start: 5
      end: 6
      range: [5, 6]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 6
    start: 0
    end: 6
    range: [0, 6]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 6

  testAstLocationData '!x',
    type: 'UnaryExpression'
    argument:
      start: 1
      end: 2
      range: [1, 2]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 2
    start: 0
    end: 2
    range: [0, 2]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 2

  testAstLocationData 'not x',
    type: 'UnaryExpression'
    argument:
      start: 4
      end: 5
      range: [4, 5]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 5
    start: 0
    end: 5
    range: [0, 5]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 5

  testAstLocationData 'x++',
    type: 'UpdateExpression'
    argument:
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    start: 0
    end: 3
    range: [0, 3]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 3

  testAstLocationData '(x + y) * z',
    type: 'BinaryExpression'
    left:
      left:
        start: 1
        end: 2
        range: [1, 2]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 2
      right:
        start: 5
        end: 6
        range: [5, 6]
        loc:
          start:
            line: 1
            column: 5
          end:
            line: 1
            column: 6
      start: 1
      end: 6
      range: [1, 6]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 6
    right:
      start: 10
      end: 11
      range: [10, 11]
      loc:
        start:
          line: 1
          column: 10
        end:
          line: 1
          column: 11
    start: 0
    end: 11
    range: [0, 11]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 11

test "AST location data as expected for Call node", ->
  testAstLocationData 'fn()',
    type: 'CallExpression'
    start: 0
    end: 4
    range: [0, 4]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 4
    callee:
      start: 0
      end: 2
      range: [0, 2]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 2

  testAstLocationData 'new Date()',
    type: 'NewExpression'
    start: 0
    end: 10
    range: [0, 10]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 10
    callee:
      start: 4
      end: 8
      range: [4, 8]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 8

  testAstLocationData '''
    new Old(
      1
    )
  ''',
    start: 0
    end: 14
    range: [0, 14]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 1
    type: 'NewExpression'
    arguments: [
      start: 11
      end: 12
      range: [11, 12]
      loc:
        start:
          line: 2
          column: 2
        end:
          line: 2
          column: 3
    ]

  testAstLocationData 'maybe? 1 + 1',
    type: 'OptionalCallExpression'
    start: 0
    end: 12
    range: [0, 12]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 12
    arguments: [
      start: 7
      end: 12
      range: [7, 12]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 12
    ]

  testAstLocationData '''
    goDo(this,
      that)
  ''',
    type: 'CallExpression'
    start: 0
    end: 18
    range: [0, 18]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 7
    arguments: [
      start: 5
      end: 9
      range: [5, 9]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 9
    ,
      start: 13
      end: 17
      range: [13, 17]
      loc:
        start:
          line: 2
          column: 2
        end:
          line: 2
          column: 6
    ]

  testAstLocationData 'new Old',
    type: 'NewExpression'
    callee:
      start: 4
      end: 7
      range: [4, 7]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 7
    start: 0
    end: 7
    range: [0, 7]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 7

test "AST location data as expected for SuperCall node", ->
  testAstLocationData 'class child extends parent then constructor: -> super()',
    type: 'ClassDeclaration'
    body:
      body: [
        body:
          body: [
            expression:
              callee:
                start: 48
                end: 53
                range: [48, 53]
                loc:
                  start:
                    line: 1
                    column: 48
                  end:
                    line: 1
                    column: 53
              start: 48
              end: 55
              range: [48, 55]
              loc:
                start:
                  line: 1
                  column: 48
                end:
                  line: 1
                  column: 55
          ]
      ]
    start: 0
    end: 55
    range: [0, 55]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 55

test "AST location data as expected for Super node", ->
  testAstLocationData '''
    class child extends parent
      func: ->
        super[prop]()
  ''',
    type: 'ClassDeclaration'
    body:
      body: [
        body:
          body: [
            expression:
              callee:
                object:
                  start: 42
                  end: 47
                  range: [42, 47]
                  loc:
                    start:
                      line: 3
                      column: 4
                    end:
                      line: 3
                      column: 9
                property:
                  start: 48
                  end: 52
                  range: [48, 52]
                  loc:
                    start:
                      line: 3
                      column: 10
                    end:
                      line: 3
                      column: 14
                start: 42
                end: 53
                range: [42, 53]
                loc:
                  start:
                    line: 3
                    column: 4
                  end:
                    line: 3
                    column: 15
              start: 42
              end: 55
              range: [42, 55]
              loc:
                start:
                  line: 3
                  column: 4
                end:
                  line: 3
                  column: 17
          ]
      ]
    start: 0
    end: 55
    range: [0, 55]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 17

test "AST location data as expected for Range node", ->
  testAstLocationData '[x..y]',
    type: 'Range'
    from:
      start: 1
      end: 2
      range: [1, 2]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 2
    to:
      start: 4
      end: 5
      range: [4, 5]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 5
    start: 0
    end: 6
    range: [0, 6]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 6

  testAstLocationData '[4...2]',
    type: 'Range'
    from:
      start: 1
      end: 2
      range: [1, 2]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 2
    to:
      start: 5
      end: 6
      range: [5, 6]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 6
    start: 0
    end: 7
    range: [0, 7]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 7

test "AST location data as expected for Slice node", ->
  testAstLocationData 'x[..y]',
    property:
      to:
        start: 4
        end: 5
        range: [4, 5]
        loc:
          start:
            line: 1
            column: 4
          end:
            line: 1
            column: 5
      start: 2
      end: 5
      range: [2, 5]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 5
    start: 0
    end: 6
    range: [0, 6]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 6

  testAstLocationData 'x[y...z]',
    property:
      start: 2
      end: 7
      range: [2, 7]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 7
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 8

  testAstLocationData 'x[...]',
    property:
      start: 2
      end: 5
      range: [2, 5]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 5
    start: 0
    end: 6
    range: [0, 6]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 6

test "AST location data as expected for Splat node", ->
  testAstLocationData '[a...]',
    type: 'ArrayExpression'
    elements: [
      argument:
        start: 1
        end: 2
        range: [1, 2]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 2
      start: 1
      end: 5
      range: [1, 5]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 5
    ]
    start: 0
    end: 6
    range: [0, 6]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 6

  testAstLocationData '[b, ...c]',
    type: 'ArrayExpression'
    elements: [
      {}
    ,
      argument:
        start: 7
        end: 8
        range: [7, 8]
        loc:
          start:
            line: 1
            column: 7
          end:
            line: 1
            column: 8
      start: 4
      end: 8
      range: [4, 8]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 8
    ]
    start: 0
    end: 9
    range: [0, 9]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 9

test "AST location data as expected for Elision node", ->
  testAstLocationData '[,,,a,, ,b]',
    type: 'ArrayExpression'
    elements: [
      null,,,
      start: 4
      end: 5
      range: [4, 5]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 5
    ,,,
      start: 9
      end: 10
      range: [9, 10]
      loc:
        start:
          line: 1
          column: 9
        end:
          line: 1
          column: 10
    ]
    start: 0
    end: 11
    range: [0, 11]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 11

test "AST location data as expected for ModuleDeclaration node", ->
  testAstLocationData 'export {X}',
    type: 'ExportNamedDeclaration'
    specifiers: [
      local:
        start: 8
        end: 9
        range: [8, 9]
        loc:
          start:
            line: 1
            column: 8
          end:
            line: 1
            column: 9
      exported:
        start: 8
        end: 9
        range: [8, 9]
        loc:
          start:
            line: 1
            column: 8
          end:
            line: 1
            column: 9
      start: 8
      end: 9
      range: [8, 9]
      loc:
        start:
          line: 1
          column: 8
        end:
          line: 1
          column: 9
    ]
    start: 0
    end: 10
    range: [0, 10]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 10

  testAstLocationData 'import X from "."',
    type: 'ImportDeclaration'
    specifiers: [
      start: 7
      end: 8
      range: [7, 8]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 8
    ]
    source:
      start: 14
      end: 17
      range: [14, 17]
      loc:
        start:
          line: 1
          column: 14
        end:
          line: 1
          column: 17
    start: 0
    end: 17
    range: [0, 17]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 17

test "AST location data as expected for ImportDeclaration node", ->
  testAstLocationData '''
    import React, {
      Component
    } from "react"
  ''',
    type: 'ImportDeclaration'
    specifiers: [
      start: 7
      end: 12
      range: [7, 12]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 12
    ,
      imported:
        start: 18
        end: 27
        range: [18, 27]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 11
      start: 18
      end: 27
      range: [18, 27]
      loc:
        start:
          line: 2
          column: 2
        end:
          line: 2
          column: 11
    ]
    source:
      start: 35
      end: 42
      range: [35, 42]
      loc:
        start:
          line: 3
          column: 7
        end:
          line: 3
          column: 14
    start: 0
    end: 42
    range: [0, 42]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 14

test "AST location data as expected for ExportNamedDeclaration node", ->
  testAstLocationData 'export {}',
    type: 'ExportNamedDeclaration'
    start: 0
    end: 9
    range: [0, 9]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 9

  testAstLocationData 'export fn = ->',
    type: 'ExportNamedDeclaration'
    declaration:
      left:
        start: 7
        end: 9
        range: [7, 9]
        loc:
          start:
            line: 1
            column: 7
          end:
            line: 1
            column: 9
      right:
        start: 12
        end: 14
        range: [12, 14]
        loc:
          start:
            line: 1
            column: 12
          end:
            line: 1
            column: 14
      start: 7
      end: 14
      range: [7, 14]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 14
    start: 0
    end: 14
    range: [0, 14]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 14

  testAstLocationData 'export class A',
    type: 'ExportNamedDeclaration'
    declaration:
      id:
        start: 13
        end: 14
        range: [13, 14]
        loc:
          start:
            line: 1
            column: 13
          end:
            line: 1
            column: 14
      start: 7
      end: 14
      range: [7, 14]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 14
    start: 0
    end: 14
    range: [0, 14]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 14

  testAstLocationData '''
    export {
      x as y
      z as default
      }
  ''',
    type: 'ExportNamedDeclaration'
    specifiers: [
      local:
        start: 11
        end: 12
        range: [11, 12]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 3
      exported:
        start: 16
        end: 17
        range: [16, 17]
        loc:
          start:
            line: 2
            column: 7
          end:
            line: 2
            column: 8
      start: 11
      end: 17
      range: [11, 17]
      loc:
        start:
          line: 2
          column: 2
        end:
          line: 2
          column: 8
    ,
      local:
        start: 20
        end: 21
        range: [20, 21]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 3
      exported:
        start: 25
        end: 32
        range: [25, 32]
        loc:
          start:
            line: 3
            column: 7
          end:
            line: 3
            column: 14
      start: 20
      end: 32
      range: [20, 32]
      loc:
        start:
          line: 3
          column: 2
        end:
          line: 3
          column: 14
    ]
    start: 0
    end: 36
    range: [0, 36]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 3

  testAstLocationData 'export {default, default as b} from "./abc"',
    type: 'ExportNamedDeclaration'
    specifiers: [
      local:
        start: 8
        end: 15
        range: [8, 15]
        loc:
          start:
            line: 1
            column: 8
          end:
            line: 1
            column: 15
      start: 8
      end: 15
      range: [8, 15]
      loc:
        start:
          line: 1
          column: 8
        end:
          line: 1
          column: 15
    ,
      local:
        start: 17
        end: 24
        range: [17, 24]
        loc:
          start:
            line: 1
            column: 17
          end:
            line: 1
            column: 24
      exported:
        start: 28
        end: 29
        range: [28, 29]
        loc:
          start:
            line: 1
            column: 28
          end:
            line: 1
            column: 29
      start: 17
      end: 29
      range: [17, 29]
      loc:
        start:
          line: 1
          column: 17
        end:
          line: 1
          column: 29
    ]
    source:
      start: 36
      end: 43
      range: [36, 43]
      loc:
        start:
          line: 1
          column: 36
        end:
          line: 1
          column: 43
    start: 0
    end: 43
    range: [0, 43]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 43

test "AST location data as expected for ExportDefaultDeclaration node", ->
  testAstLocationData 'export default class',
    type: 'ExportDefaultDeclaration'
    declaration:
      start: 15
      end: 20
      range: [15, 20]
      loc:
        start:
          line: 1
          column: 15
        end:
          line: 1
          column: 20
    start: 0
    end: 20
    range: [0, 20]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 20

  testAstLocationData 'export default "abc"',
    type: 'ExportDefaultDeclaration'
    declaration:
      start: 15
      end: 20
      range: [15, 20]
      loc:
        start:
          line: 1
          column: 15
        end:
          line: 1
          column: 20
    start: 0
    end: 20
    range: [0, 20]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 20

test "AST location data as expected for ExportAllDeclaration node", ->
  testAstLocationData 'export * from "module-name"',
    type: 'ExportAllDeclaration'
    source:
      start: 14
      end: 27
      range: [14, 27]
      loc:
        start:
          line: 1
          column: 14
        end:
          line: 1
          column: 27
    start: 0
    end: 27
    range: [0, 27]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 27

test "AST location data as expected for ImportDefaultSpecifier node", ->
  testAstLocationData 'import React from "react"',
    type: 'ImportDeclaration'
    specifiers: [
      start: 7
      end: 12
      range: [7, 12]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 12
    ]
    source:
      start: 18
      end: 25
      range: [18, 25]
      loc:
        start:
          line: 1
          column: 18
        end:
          line: 1
          column: 25
    start: 0
    end: 25
    range: [0, 25]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 25

test "AST location data as expected for ImportNamespaceSpecifier node", ->
  testAstLocationData 'import * as React from "react"',
    type: 'ImportDeclaration'
    specifiers: [
      start: 7
      end: 17
      range: [7, 17]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 17
    ]
    source:
      start: 23
      end: 30
      range: [23, 30]
      loc:
        start:
          line: 1
          column: 23
        end:
          line: 1
          column: 30
    start: 0
    end: 30
    range: [0, 30]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 30

  testAstLocationData 'import React, * as ReactStar from "react"',
    type: 'ImportDeclaration'
    specifiers: [
      start: 7
      end: 12
      range: [7, 12]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 12
    ,
      local:
        start: 19
        end: 28
        range: [19, 28]
        loc:
          start:
            line: 1
            column: 19
          end:
            line: 1
            column: 28
      start: 14
      end: 28
      range: [14, 28]
      loc:
        start:
          line: 1
          column: 14
        end:
          line: 1
          column: 28
    ]
    source:
      start: 34
      end: 41
      range: [34, 41]
      loc:
        start:
          line: 1
          column: 34
        end:
          line: 1
          column: 41
    start: 0
    end: 41
    range: [0, 41]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 41

test "AST location data as expected for Obj node", ->
  testAstLocationData "{a: 1, b, [c], @d, [e()]: f, 'g': 2, ...h, i...}",
    type: 'ObjectExpression'
    properties: [
      key:
        start: 1
        end: 2
        range: [1, 2]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 2
      value:
        start: 4
        end: 5
        range: [4, 5]
        loc:
          start:
            line: 1
            column: 4
          end:
            line: 1
            column: 5
      start: 1
      end: 5
      range: [1, 5]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 5
    ,
      key:
        start: 7
        end: 8
        range: [7, 8]
        loc:
          start:
            line: 1
            column: 7
          end:
            line: 1
            column: 8
      value:
        start: 7
        end: 8
        range: [7, 8]
        loc:
          start:
            line: 1
            column: 7
          end:
            line: 1
            column: 8
      start: 7
      end: 8
      range: [7, 8]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 8
    ,
      key:
        start: 11
        end: 12
        range: [11, 12]
        loc:
          start:
            line: 1
            column: 11
          end:
            line: 1
            column: 12
      value:
        start: 11
        end: 12
        range: [11, 12]
        loc:
          start:
            line: 1
            column: 11
          end:
            line: 1
            column: 12
      start: 10
      end: 13
      range: [10, 13]
      loc:
        start:
          line: 1
          column: 10
        end:
          line: 1
          column: 13
    ,
      key:
        object:
          start: 15
          end: 16
          range: [15, 16]
          loc:
            start:
              line: 1
              column: 15
            end:
              line: 1
              column: 16
        property:
          start: 16
          end: 17
          range: [16, 17]
          loc:
            start:
              line: 1
              column: 16
            end:
              line: 1
              column: 17
        start: 15
        end: 17
        range: [15, 17]
        loc:
          start:
            line: 1
            column: 15
          end:
            line: 1
            column: 17
      value:
        object:
          start: 15
          end: 16
          range: [15, 16]
          loc:
            start:
              line: 1
              column: 15
            end:
              line: 1
              column: 16
        property:
          start: 16
          end: 17
          range: [16, 17]
          loc:
            start:
              line: 1
              column: 16
            end:
              line: 1
              column: 17
        start: 15
        end: 17
        range: [15, 17]
        loc:
          start:
            line: 1
            column: 15
          end:
            line: 1
            column: 17
      start: 15
      end: 17
      range: [15, 17]
      loc:
        start:
          line: 1
          column: 15
        end:
          line: 1
          column: 17
    ,
      key:
        start: 20
        end: 23
        range: [20, 23]
        loc:
          start:
            line: 1
            column: 20
          end:
            line: 1
            column: 23
      value:
        start: 26
        end: 27
        range: [26, 27]
        loc:
          start:
            line: 1
            column: 26
          end:
            line: 1
            column: 27
      start: 19
      end: 27
      range: [19, 27]
      loc:
        start:
          line: 1
          column: 19
        end:
          line: 1
          column: 27
    ,
      key:
        start: 29
        end: 32
        range: [29, 32]
        loc:
          start:
            line: 1
            column: 29
          end:
            line: 1
            column: 32
      value:
        start: 34
        end: 35
        range: [34, 35]
        loc:
          start:
            line: 1
            column: 34
          end:
            line: 1
            column: 35
      start: 29
      end: 35
      range: [29, 35]
      loc:
        start:
          line: 1
          column: 29
        end:
          line: 1
          column: 35
    ,
      argument:
        start: 40
        end: 41
        range: [40, 41]
        loc:
          start:
            line: 1
            column: 40
          end:
            line: 1
            column: 41
      start: 37
      end: 41
      range: [37, 41]
      loc:
        start:
          line: 1
          column: 37
        end:
          line: 1
          column: 41
    ,
      argument:
        start: 43
        end: 44
        range: [43, 44]
        loc:
          start:
            line: 1
            column: 43
          end:
            line: 1
            column: 44
      start: 43
      end: 47
      range: [43, 47]
      loc:
        start:
          line: 1
          column: 43
        end:
          line: 1
          column: 47
    ]
    start: 0
    end: 48
    range: [0, 48]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 48

  testAstLocationData 'a: 1',
    type: 'ObjectExpression'
    properties: [
      key:
        start: 0
        end: 1
        range: [0, 1]
        loc:
          start:
            line: 1
            column: 0
          end:
            line: 1
            column: 1
      value:
        start: 3
        end: 4
        range: [3, 4]
        loc:
          start:
            line: 1
            column: 3
          end:
            line: 1
            column: 4
      start: 0
      end: 4
      range: [0, 4]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 4
    ]
    start: 0
    end: 4
    range: [0, 4]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 4

test "AST location data as expected for Assign node", ->
  testAstLocationData 'a = b',
    type: 'AssignmentExpression'
    left:
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    right:
      start: 4
      end: 5
      range: [4, 5]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 5
    start: 0
    end: 5
    range: [0, 5]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 5

  testAstLocationData 'a += b',
    type: 'AssignmentExpression'
    left:
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    right:
      start: 5
      end: 6
      range: [5, 6]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 6
    start: 0
    end: 6
    range: [0, 6]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 6

  testAstLocationData '{a: [...b]} = c',
    type: 'AssignmentExpression'
    left:
      properties: [
        type: 'ObjectProperty'
        key:
          start: 1
          end: 2
          range: [1, 2]
          loc:
            start:
              line: 1
              column: 1
            end:
              line: 1
              column: 2
        value:
          elements: [
            start: 5
            end: 9
            range: [5, 9]
            loc:
              start:
                line: 1
                column: 5
              end:
                line: 1
                column: 9
          ]
          start: 4
          end: 10
          range: [4, 10]
          loc:
            start:
              line: 1
              column: 4
            end:
              line: 1
              column: 10
        start: 1
        end: 10
        range: [1, 10]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 10
      ]
      start: 0
      end: 11
      range: [0, 11]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 11
    right:
      start: 14
      end: 15
      range: [14, 15]
      loc:
        start:
          line: 1
          column: 14
        end:
          line: 1
          column: 15
    start: 0
    end: 15
    range: [0, 15]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 15

test "AST location data as expected for Expansion node", ->
  testAstLocationData '[..., b] = c',
    type: 'AssignmentExpression'
    left:
      elements: [
        start: 1
        end: 4
        range: [1, 4]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 4
      ]
      start: 0
      end: 8
      range: [0, 8]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 8
    start: 0
    end: 12
    range: [0, 12]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 12

test "AST location data as expected for Throw node", ->
  testAstLocationData 'throw new BallError "catch"',
    type: 'ThrowStatement'
    argument:
      start: 6
      end: 27
      range: [6, 27]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 27
    start: 0
    end: 27
    range: [0, 27]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 27

test "AST location data as expected for Existence node", ->
  testAstLocationData 'Ghosts?',
    type: 'UnaryExpression'
    argument:
      start: 0
      end: 6
      range: [0, 6]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 6
    start: 0
    end: 7
    range: [0, 7]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 7

test "AST location data as expected for JSXTag node", ->
  testAstLocationData '<CSXY />',
    type: 'JSXElement'
    openingElement:
      name:
        start: 1
        end: 5
        range: [1, 5]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 5
      start: 0
      end: 8
      range: [0, 8]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 8
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 8

  testAstLocationData '<div></div>',
    type: 'JSXElement'
    openingElement:
      name:
        start: 1
        end: 4
        range: [1, 4]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 4
      start: 0
      end: 5
      range: [0, 5]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 5
    closingElement:
      name:
        start: 7
        end: 10
        range: [7, 10]
        loc:
          start:
            line: 1
            column: 7
          end:
            line: 1
            column: 10
      start: 5
      end: 11
      range: [5, 11]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 11
    start: 0
    end: 11
    range: [0, 11]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 11

  testAstLocationData '<A.B />',
    type: 'JSXElement'
    openingElement:
      name:
        object:
          start: 1
          end: 2
          range: [1, 2]
          loc:
            start:
              line: 1
              column: 1
            end:
              line: 1
              column: 2
        property:
          start: 3
          end: 4
          range: [3, 4]
          loc:
            start:
              line: 1
              column: 3
            end:
              line: 1
              column: 4
        start: 1
        end: 4
        range: [1, 4]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 4
      start: 0
      end: 7
      range: [0, 7]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 7
    start: 0
    end: 7
    range: [0, 7]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 7

  testAstLocationData '<Tag.Name.Here></Tag.Name.Here>',
    type: 'JSXElement'
    openingElement:
      name:
        object:
          object:
            start: 1
            end: 4
            range: [1, 4]
            loc:
              start:
                line: 1
                column: 1
              end:
                line: 1
                column: 4
          property:
            start: 5
            end: 9
            range: [5, 9]
            loc:
              start:
                line: 1
                column: 5
              end:
                line: 1
                column: 9
          start: 1
          end: 9
          range: [1, 9]
          loc:
            start:
              line: 1
              column: 1
            end:
              line: 1
              column: 9
        property:
          start: 10
          end: 14
          range: [10, 14]
          loc:
            start:
              line: 1
              column: 10
            end:
              line: 1
              column: 14
        start: 1
        end: 14
        range: [1, 14]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 14
      start: 0
      end: 15
      range: [0, 15]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 15
    closingElement:
      name:
        object:
          object:
            start: 17
            end: 20
            range: [17, 20]
            loc:
              start:
                line: 1
                column: 17
              end:
                line: 1
                column: 20
          property:
            start: 21
            end: 25
            range: [21, 25]
            loc:
              start:
                line: 1
                column: 21
              end:
                line: 1
                column: 25
          start: 17
          end: 25
          range: [17, 25]
          loc:
            start:
              line: 1
              column: 17
            end:
              line: 1
              column: 25
        property:
          start: 26
          end: 30
          range: [26, 30]
          loc:
            start:
              line: 1
              column: 26
            end:
              line: 1
              column: 30
        start: 17
        end: 30
        range: [17, 30]
        loc:
          start:
            line: 1
            column: 17
          end:
            line: 1
            column: 30
      start: 15
      end: 31
      range: [15, 31]
      loc:
        start:
          line: 1
          column: 15
        end:
          line: 1
          column: 31
    start: 0
    end: 31
    range: [0, 31]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 31

  testAstLocationData '<></>',
    type: 'JSXFragment'
    openingFragment:
      start: 0
      end: 2
      range: [0, 2]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 2
    closingFragment:
      start: 2
      end: 5
      range: [2, 5]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 5
    start: 0
    end: 5
    range: [0, 5]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 5

  testAstLocationData '''
    <div
      a
      b="c"
      d={e}
      {...f}
    />
  ''',
    type: 'JSXElement'
    openingElement:
      name:
        start: 1
        end: 4
        range: [1, 4]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 4
      attributes: [
        name:
          start: 7
          end: 8
          range: [7, 8]
          loc:
            start:
              line: 2
              column: 2
            end:
              line: 2
              column: 3
        start: 7
        end: 8
        range: [7, 8]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 3
      ,
        name:
          start: 11
          end: 12
          range: [11, 12]
          loc:
            start:
              line: 3
              column: 2
            end:
              line: 3
              column: 3
        value:
          start: 13
          end: 16
          range: [13, 16]
          loc:
            start:
              line: 3
              column: 4
            end:
              line: 3
              column: 7
        start: 11
        end: 16
        range: [11, 16]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 7
      ,
        name:
          start: 19
          end: 20
          range: [19, 20]
          loc:
            start:
              line: 4
              column: 2
            end:
              line: 4
              column: 3
        value:
          expression:
            start: 22
            end: 23
            range: [22, 23]
            loc:
              start:
                line: 4
                column: 5
              end:
                line: 4
                column: 6
          start: 21
          end: 24
          range: [21, 24]
          loc:
            start:
              line: 4
              column: 4
            end:
              line: 4
              column: 7
        start: 19
        end: 24
        range: [19, 24]
        loc:
          start:
            line: 4
            column: 2
          end:
            line: 4
            column: 7
      ,
        argument:
          start: 31
          end: 32
          range: [31, 32]
          loc:
            start:
              line: 5
              column: 6
            end:
              line: 5
              column: 7
        start: 27
        end: 33
        range: [27, 33]
        loc:
          start:
            line: 5
            column: 2
          end:
            line: 5
            column: 8
      ]
      start: 0
      end: 36
      range: [0, 36]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 6
          column: 2
    start: 0
    end: 36
    range: [0, 36]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 6
        column: 2

  testAstLocationData '<div {f...} />',
    type: 'JSXElement'
    openingElement:
      attributes: [
        argument:
          start: 6
          end: 7
          range: [6, 7]
          loc:
            start:
              line: 1
              column: 6
            end:
              line: 1
              column: 7
        start: 5
        end: 11
        range: [5, 11]
        loc:
          start:
            line: 1
            column: 5
          end:
            line: 1
            column: 11
      ]

  testAstLocationData '<div>abc</div>',
    type: 'JSXElement'
    openingElement:
      name:
        start: 1
        end: 4
        range: [1, 4]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 4
      start: 0
      end: 5
      range: [0, 5]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 5
    closingElement:
      name:
        start: 10
        end: 13
        range: [10, 13]
        loc:
          start:
            line: 1
            column: 10
          end:
            line: 1
            column: 13
      start: 8
      end: 14
      range: [8, 14]
      loc:
        start:
          line: 1
          column: 8
        end:
          line: 1
          column: 14
    children: [
      start: 5
      end: 8
      range: [5, 8]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 8
    ]
    start: 0
    end: 14
    range: [0, 14]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 14

  testAstLocationData '''
    <a>
      {b}
      <c />
    </a>
  ''',
    type: 'JSXElement'
    openingElement:
      name:
        start: 1
        end: 2
        range: [1, 2]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 2
      start: 0
      end: 3
      range: [0, 3]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 3
    closingElement:
      name:
        start: 20
        end: 21
        range: [20, 21]
        loc:
          start:
            line: 4
            column: 2
          end:
            line: 4
            column: 3
      start: 18
      end: 22
      range: [18, 22]
      loc:
        start:
          line: 4
          column: 0
        end:
          line: 4
          column: 4
    children: [
      start: 3
      end: 6
      range: [3, 6]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 2
          column: 2
    ,
      expression:
        start: 7
        end: 8
        range: [7, 8]
        loc:
          start:
            line: 2
            column: 3
          end:
            line: 2
            column: 4
      start: 6
      end: 9
      range: [6, 9]
      loc:
        start:
          line: 2
          column: 2
        end:
          line: 2
          column: 5
    ,
      start: 9
      end: 12
      range: [9, 12]
      loc:
        start:
          line: 2
          column: 5
        end:
          line: 3
          column: 2
    ,
      start: 12
      end: 17
      range: [12, 17]
      loc:
        start:
          line: 3
          column: 2
        end:
          line: 3
          column: 7
    ,
      start: 17
      end: 18
      range: [17, 18]
      loc:
        start:
          line: 3
          column: 7
        end:
          line: 4
          column: 0
    ]
    start: 0
    end: 22
    range: [0, 22]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 4

  testAstLocationData '<>abc{}</>',
    type: 'JSXFragment'
    openingFragment:
      start: 0
      end: 2
      range: [0, 2]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 2
    closingFragment:
      start: 7
      end: 10
      range: [7, 10]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 10
    children: [
      start: 2
      end: 5
      range: [2, 5]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 5
    ,
      expression:
        start: 6
        end: 6
        range: [6, 6]
        loc:
          start:
            line: 1
            column: 6
          end:
            line: 1
            column: 6
      start: 5
      end: 7
      range: [5, 7]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 7
    ]
    start: 0
    end: 10
    range: [0, 10]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 10

  testAstLocationData '''
    <a>{<b />}</a>
  ''',
    type: 'JSXElement'
    children: [
      expression:
        start: 4
        end: 9
        range: [4, 9]
        loc:
          start:
            line: 1
            column: 4
          end:
            line: 1
            column: 9
      start: 3
      end: 10
      range: [3, 10]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 1
          column: 10
    ]
    start: 0
    end: 14
    range: [0, 14]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 14

  testAstLocationData '''
    <div>{
      # comment
    }</div>
  ''',
    type: 'JSXElement'
    children: [
      expression:
        start: 6
        end: 19
        range: [6, 19]
        loc:
          start:
            line: 1
            column: 6
          end:
            line: 3
            column: 0
      start: 5
      end: 20
      range: [5, 20]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 3
          column: 1
    ]
    start: 0
    end: 26
    range: [0, 26]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 7

  testAstLocationData '''
    <div>{### here ###}</div>
  ''',
    type: 'JSXElement'
    children: [
      expression:
        start: 6
        end: 18
        range: [6, 18]
        loc:
          start:
            line: 1
            column: 6
          end:
            line: 1
            column: 18
      start: 5
      end: 19
      range: [5, 19]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 19
    ]
    start: 0
    end: 25
    range: [0, 25]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 25

  testAstLocationData '<div:a b:c />',
    type: 'JSXElement'
    openingElement:
      name:
        namespace:
          start: 1
          end: 4
          range: [1, 4]
          loc:
            start:
              line: 1
              column: 1
            end:
              line: 1
              column: 4
        name:
          start: 5
          end: 6
          range: [5, 6]
          loc:
            start:
              line: 1
              column: 5
            end:
              line: 1
              column: 6
        start: 1
        end: 6
        range: [1, 6]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 6
      attributes: [
        name:
          namespace:
            start: 7
            end: 8
            range: [7, 8]
            loc:
              start:
                line: 1
                column: 7
              end:
                line: 1
                column: 8
          name:
            start: 9
            end: 10
            range: [9, 10]
            loc:
              start:
                line: 1
                column: 9
              end:
                line: 1
                column: 10
          start: 7
          end: 10
          range: [7, 10]
          loc:
            start:
              line: 1
              column: 7
            end:
              line: 1
              column: 10
        start: 7
        end: 10
        range: [7, 10]
        loc:
          start:
            line: 1
            column: 7
          end:
            line: 1
            column: 10
      ]
      start: 0
      end: 13
      range: [0, 13]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 13
    start: 0
    end: 13
    range: [0, 13]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 13

  testAstLocationData '''
    <div:a>
      {b}
    </div:a>
  ''',
    type: 'JSXElement'
    openingElement:
      name:
        namespace:
          start: 1
          end: 4
          range: [1, 4]
          loc:
            start:
              line: 1
              column: 1
            end:
              line: 1
              column: 4
        name:
          start: 5
          end: 6
          range: [5, 6]
          loc:
            start:
              line: 1
              column: 5
            end:
              line: 1
              column: 6
        start: 1
        end: 6
        range: [1, 6]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 6
      start: 0
      end: 7
      range: [0, 7]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 7
    closingElement:
      name:
        namespace:
          start: 16
          end: 19
          range: [16, 19]
          loc:
            start:
              line: 3
              column: 2
            end:
              line: 3
              column: 5
        name:
          start: 20
          end: 21
          range: [20, 21]
          loc:
            start:
              line: 3
              column: 6
            end:
              line: 3
              column: 7
        start: 16
        end: 21
        range: [16, 21]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 7
      start: 14
      end: 22
      range: [14, 22]
      loc:
        start:
          line: 3
          column: 0
        end:
          line: 3
          column: 8
    start: 0
    end: 22
    range: [0, 22]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 8

  testAstLocationData '''
    <div.a>
      {b}
    </div.a>
  ''',
    type: 'JSXElement'
    openingElement:
      name:
        object:
          start: 1
          end: 4
          range: [1, 4]
          loc:
            start:
              line: 1
              column: 1
            end:
              line: 1
              column: 4
        property:
          start: 5
          end: 6
          range: [5, 6]
          loc:
            start:
              line: 1
              column: 5
            end:
              line: 1
              column: 6
        start: 1
        end: 6
        range: [1, 6]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 6
      start: 0
      end: 7
      range: [0, 7]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 7
    closingElement:
      name:
        object:
          start: 16
          end: 19
          range: [16, 19]
          loc:
            start:
              line: 3
              column: 2
            end:
              line: 3
              column: 5
        property:
          start: 20
          end: 21
          range: [20, 21]
          loc:
            start:
              line: 3
              column: 6
            end:
              line: 3
              column: 7
        start: 16
        end: 21
        range: [16, 21]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 7
      start: 14
      end: 22
      range: [14, 22]
      loc:
        start:
          line: 3
          column: 0
        end:
          line: 3
          column: 8
    start: 0
    end: 22
    range: [0, 22]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 8

test "AST as expected for Try node", ->
  testAstLocationData 'try cappuccino',
    type: 'TryStatement'
    block:
      type: 'BlockStatement'
      body: [
        expression:
          start: 4
          end: 14
          range: [4, 14]
          loc:
            start:
              line: 1
              column: 4
            end:
              line: 1
              column: 14
        start: 4
        end: 14
        range: [4, 14]
        loc:
          start:
            line: 1
            column: 4
          end:
            line: 1
            column: 14
      ]
      start: 3
      end: 14
      range: [3, 14]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 1
          column: 14
    start: 0
    end: 14
    range: [0, 14]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 14

  testAstLocationData '''
    try
      x = 1
      y()
    catch e
      d()
    finally
      f + g
  ''',
    type: 'TryStatement'
    block:
      type: 'BlockStatement'
      body: [
        expression:
          start: 6
          end: 11
          range: [6, 11]
          loc:
            start:
              line: 2
              column: 2
            end:
              line: 2
              column: 7
        start: 6
        end: 11
        range: [6, 11]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 7
      ,
        expression:
          start: 14
          end: 17
          range: [14, 17]
          loc:
            start:
              line: 3
              column: 2
            end:
              line: 3
              column: 5
        start: 14
        end: 17
        range: [14, 17]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 5
      ]
      start: 4
      end: 17
      range: [4, 17]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 5
    handler:
      param:
        start: 24
        end: 25
        range: [24, 25]
        loc:
          start:
            line: 4
            column: 6
          end:
            line: 4
            column: 7
      body:
        body: [
          start: 28
          end: 31
          range: [28, 31]
          loc:
            start:
              line: 5
              column: 2
            end:
              line: 5
              column: 5
        ]
        start: 26
        end: 31
        range: [26, 31]
        loc:
          start:
            line: 5
            column: 0
          end:
            line: 5
            column: 5
      start: 18
      end: 31
      range: [18, 31]
      loc:
        start:
          line: 4
          column: 0
        end:
          line: 5
          column: 5
    finalizer:
      body: [
        expression:
          start: 42
          end: 47
          range: [42, 47]
          loc:
            start:
              line: 7
              column: 2
            end:
              line: 7
              column: 7
        start: 42
        end: 47
        range: [42, 47]
        loc:
          start:
            line: 7
            column: 2
          end:
            line: 7
            column: 7
      ]
      start: 32
      end: 47
      range: [32, 47]
      loc:
        start:
          line: 6
          column: 0
        end:
          line: 7
          column: 7
    start: 0
    end: 47
    range: [0, 47]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 7
        column: 7

  testAstLocationData '''
    try
    catch {e}
      f
  ''',
    type: 'TryStatement'
    handler:
      param:
        start: 10
        end: 13
        range: [10, 13]
        loc:
          start:
            line: 2
            column: 6
          end:
            line: 2
            column: 9
      body:
        start: 14
        end: 17
        range: [14, 17]
        loc:
          start:
            line: 3
            column: 0
          end:
            line: 3
            column: 3
      start: 4
      end: 17
      range: [4, 17]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 3

test "AST location data as expected for Root node", ->
  testAstRootLocationData '1\n2',
    type: 'File'
    program:
      start: 0
      end: 3
      range: [0, 3]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 2
          column: 1
    start: 0
    end: 3
    range: [0, 3]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 1

  testAstRootLocationData 'a = 1\nb',
    type: 'File'
    program:
      start: 0
      end: 7
      range: [0, 7]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 2
          column: 1
    start: 0
    end: 7
    range: [0, 7]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 1

  testAstRootLocationData 'a = 1\nb\n\n',
    type: 'File'
    program:
      start: 0
      end: 9
      range: [0, 9]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 4
          column: 0
    start: 0
    end: 9
    range: [0, 9]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 0

  testAstRootLocationData 'a = 1\n\n# Comment',
    type: 'File'
    program:
      start: 0
      end: 16
      range: [0, 16]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 3
          column: 9
    start: 0
    end: 16
    range: [0, 16]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 9

  testAstRootLocationData 'a = 1\n\n# Comment\n',
    type: 'File'
    program:
      start: 0
      end: 17
      range: [0, 17]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 4
          column: 0
    start: 0
    end: 17
    range: [0, 17]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 0

  testAstRootLocationData '''
    # comment
    "use strict"
  ''',
    type: 'File'
    program:
      start: 0
      end: 22
      range: [0, 22]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 2
          column: 12
    start: 0
    end: 22
    range: [0, 22]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 12

  testAstRootLocationData ' \n',
    type: 'File'
    program:
      start: 0
      end: 2
      range: [0, 2]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 2
          column: 0
    start: 0
    end: 2
    range: [0, 2]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 0

  testAstRootLocationData '\n',
    type: 'File'
    program:
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 2
          column: 0
    start: 0
    end: 1
    range: [0, 1]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 0

  testAstRootLocationData '',
    type: 'File'
    program:
      start: 0
      end: 0
      range: [0, 0]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 0
    start: 0
    end: 0
    range: [0, 0]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 0

  testAstRootLocationData ' ',
    type: 'File'
    program:
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    start: 0
    end: 1
    range: [0, 1]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 1

test "AST location data as expected for Switch node", ->
  testAstLocationData '''
    switch x
      when a then a
      when b, c then c
      else 42
  ''',
    type: 'SwitchStatement'
    discriminant:
      start: 7
      end: 8
      range: [7, 8]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 8
    cases: [
      test:
        start: 16
        end: 17
        range: [16, 17]
        loc:
          start:
            line: 2
            column: 7
          end:
            line: 2
            column: 8
      consequent: [
        expression:
          start: 23
          end: 24
          range: [23, 24]
          loc:
            start:
              line: 2
              column: 14
            end:
              line: 2
              column: 15
        start: 23
        end: 24
        range: [23, 24]
        loc:
          start:
            line: 2
            column: 14
          end:
            line: 2
            column: 15
      ]
      start: 11
      end: 24
      range: [11, 24]
      loc:
        start:
          line: 2
          column: 2
        end:
          line: 2
          column: 15
    ,
      test:
        start: 32
        end: 33
        range: [32, 33]
        loc:
          start:
            line: 3
            column: 7
          end:
            line: 3
            column: 8
      start: 27
      end: 33
      range: [27, 33]
      loc:
        start:
          line: 3
          column: 2
        end:
          line: 3
          column: 8
    ,
      test:
        start: 35
        end: 36
        range: [35, 36]
        loc:
          start:
            line: 3
            column: 10
          end:
            line: 3
            column: 11
      consequent: [
        expression:
          start: 42
          end: 43
          range: [42, 43]
          loc:
            start:
              line: 3
              column: 17
            end:
              line: 3
              column: 18
        start: 42
        end: 43
        range: [42, 43]
        loc:
          start:
            line: 3
            column: 17
          end:
            line: 3
            column: 18
      ]
      start: 35
      end: 43
      range: [35, 43]
      loc:
        start:
          line: 3
          column: 10
        end:
          line: 3
          column: 18
    ,
      consequent: [
        expression:
          start: 51
          end: 53
          range: [51, 53]
          loc:
            start:
              line: 4
              column: 7
            end:
              line: 4
              column: 9
        start: 51
        end: 53
        range: [51, 53]
        loc:
          start:
            line: 4
            column: 7
          end:
            line: 4
            column: 9
      ]
      start: 46
      end: 53
      range: [46, 53]
      loc:
        start:
          line: 4
          column: 2
        end:
          line: 4
          column: 9
    ,
    ]
    start: 0
    end: 53
    range: [0, 53]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 9

  testAstLocationData '''
    switch
      when some(condition)
        doSomething()
        andThenSomethingElse
  ''',
    type: 'SwitchStatement'
    cases: [
      test:
        start: 14
        end: 29
        range: [14, 29]
        loc:
          start:
            line: 2
            column: 7
          end:
            line: 2
            column: 22
      consequent: [
        expression:
          start: 34
          end: 47
          range: [34, 47]
          loc:
            start:
              line: 3
              column: 4
            end:
              line: 3
              column: 17
        start: 34
        end: 47
        range: [34, 47]
        loc:
          start:
            line: 3
            column: 4
          end:
            line: 3
            column: 17
      ,
        expression:
          start: 52
          end: 72
          range: [52, 72]
          loc:
            start:
              line: 4
              column: 4
            end:
              line: 4
              column: 24
      ]
    ]
test "AST location data as expected for Code node", ->
  testAstLocationData '''
    (a) ->
      b
      c()
  ''',
    type: 'FunctionExpression'
    params: [
      start: 1
      end: 2
      range: [1, 2]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 2
    ]
    body:
      body: [
        start: 9
        end: 10
        range: [9, 10]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 3
      ,
        start: 13
        end: 16
        range: [13, 16]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 5
      ]
      start: 7
      end: 16
      range: [7, 16]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 5

  testAstLocationData '''
    -> a
  ''',
    type: 'FunctionExpression'
    body:
      start: 2
      end: 4
      range: [2, 4]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 4
    start: 0
    end: 4
    range: [0, 4]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 4

  testAstLocationData '''
    (
      a,
      [
        b
        c
      ]
    ) ->
      d
  ''',
    type: 'FunctionExpression'
    params: [
      start: 4
      end: 5
      range: [4, 5]
      loc:
        start:
          line: 2
          column: 2
        end:
          line: 2
          column: 3
    ,
      elements: [
        start: 15
        end: 16
        range: [15, 16]
        loc:
          start:
            line: 4
            column: 4
          end:
            line: 4
            column: 5
      ,
        start: 21
        end: 22
        range: [21, 22]
        loc:
          start:
            line: 5
            column: 4
          end:
            line: 5
            column: 5
      ]
      start: 9
      end: 26
      range: [9, 26]
      loc:
        start:
          line: 3
          column: 2
        end:
          line: 6
          column: 3
    ]
    start: 0
    end: 35
    range: [0, 35]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 8
        column: 3

  testAstLocationData '''
    ->
  ''',
    type: 'FunctionExpression'
    body:
      start: 2
      end: 2
      range: [2, 2]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 2
    start: 0
    end: 2
    range: [0, 2]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 2

  testAstLocationData '''
    (a...) ->
  ''',
    type: 'FunctionExpression'
    params: [
      argument:
        start: 1
        end: 2
        range: [1, 2]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 2
      start: 1
      end: 5
      range: [1, 5]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 5
    ]
    start: 0
    end: 9
    range: [0, 9]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 9

  testAstLocationData '''
    (...a) ->
  ''',
    type: 'FunctionExpression'
    params: [
      argument:
        start: 4
        end: 5
        range: [4, 5]
        loc:
          start:
            line: 1
            column: 4
          end:
            line: 1
            column: 5
      start: 1
      end: 5
      range: [1, 5]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 5
    ]
    start: 0
    end: 9
    range: [0, 9]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 9

test "AST location data as expected for Return node", ->
  testAstLocationData 'return no',
    type: 'ReturnStatement'
    argument:
      start: 7
      end: 9
      range: [7, 9]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 9
    start: 0
    end: 9
    range: [0, 9]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 9

  testAstLocationData '''
    (a, b) ->
      return a + b
  ''',
    type: 'FunctionExpression'
    body:
      body: [
        argument:
          start: 19
          end: 24
          range: [19, 24]
          loc:
            start:
              line: 2
              column: 9
            end:
              line: 2
              column: 14
        start: 12
        end: 24
        range: [12, 24]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 14
      ]
      start: 10
      end: 24
      range: [10, 24]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 2
          column: 14
    start: 0
    end: 24
    range: [0, 24]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 14

  testAstLocationData '-> return',
    type: 'FunctionExpression'
    body:
      body: [
        start: 3
        end: 9
        range: [3, 9]
        loc:
          start:
            line: 1
            column: 3
          end:
            line: 1
            column: 9
      ]
      start: 2
      end: 9
      range: [2, 9]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 9
    start: 0
    end: 9
    range: [0, 9]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 9

test "AST as expected for YieldReturn node", ->
  testAstLocationData '-> yield return 1',
    type: 'FunctionExpression'
    body:
      body: [
        expression:
          argument:
            argument:
              start: 16
              end: 17
              range: [16, 17]
              loc:
                start:
                  line: 1
                  column: 16
                end:
                  line: 1
                  column: 17
            start: 9
            end: 17
            range: [9, 17]
            loc:
              start:
                line: 1
                column: 9
              end:
                line: 1
                column: 17
          start: 3
          end: 17
          range: [3, 17]
          loc:
            start:
              line: 1
              column: 3
            end:
              line: 1
              column: 17
        start: 3
        end: 17
        range: [3, 17]
        loc:
          start:
            line: 1
            column: 3
          end:
            line: 1
            column: 17
      ]
      start: 2
      end: 17
      range: [2, 17]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 17
    start: 0
    end: 17
    range: [0, 17]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 17

  testAstLocationData '-> yield return',
    type: 'FunctionExpression'
    body:
      body: [
        expression:
          argument:
            start: 9
            end: 15
            range: [9, 15]
            loc:
              start:
                line: 1
                column: 9
              end:
                line: 1
                column: 15
          start: 3
          end: 15
          range: [3, 15]
          loc:
            start:
              line: 1
              column: 3
            end:
              line: 1
              column: 15
        start: 3
        end: 15
        range: [3, 15]
        loc:
          start:
            line: 1
            column: 3
          end:
            line: 1
            column: 15
      ]
      start: 2
      end: 15
      range: [2, 15]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 15
    start: 0
    end: 15
    range: [0, 15]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 15

test "AST as expected for AwaitReturn node", ->
  testAstLocationData '-> await return 1',
    type: 'FunctionExpression'
    body:
      body: [
        expression:
          argument:
            argument:
              start: 16
              end: 17
              range: [16, 17]
              loc:
                start:
                  line: 1
                  column: 16
                end:
                  line: 1
                  column: 17
            start: 9
            end: 17
            range: [9, 17]
            loc:
              start:
                line: 1
                column: 9
              end:
                line: 1
                column: 17
          start: 3
          end: 17
          range: [3, 17]
          loc:
            start:
              line: 1
              column: 3
            end:
              line: 1
              column: 17
        start: 3
        end: 17
        range: [3, 17]
        loc:
          start:
            line: 1
            column: 3
          end:
            line: 1
            column: 17
      ]
      start: 2
      end: 17
      range: [2, 17]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 17
    start: 0
    end: 17
    range: [0, 17]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 17

  testAstLocationData '-> await return',
    type: 'FunctionExpression'
    body:
      body: [
        expression:
          argument:
            start: 9
            end: 15
            range: [9, 15]
            loc:
              start:
                line: 1
                column: 9
              end:
                line: 1
                column: 15
          start: 3
          end: 15
          range: [3, 15]
          loc:
            start:
              line: 1
              column: 3
            end:
              line: 1
              column: 15
        start: 3
        end: 15
        range: [3, 15]
        loc:
          start:
            line: 1
            column: 3
          end:
            line: 1
            column: 15
      ]
      start: 2
      end: 15
      range: [2, 15]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 15
    start: 0
    end: 15
    range: [0, 15]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 15

test "AST as expected for If node", ->
  testAstLocationData 'if maybe then yes',
    type: 'IfStatement'
    test:
      start: 3
      end: 8
      range: [3, 8]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 1
          column: 8
    consequent:
      body: [
        expression:
          start: 14
          end: 17
          range: [14, 17]
          loc:
            start:
              line: 1
              column: 14
            end:
              line: 1
              column: 17
        start: 14
        end: 17
        range: [14, 17]
        loc:
          start:
            line: 1
            column: 14
          end:
            line: 1
            column: 17
      ]
      start: 9
      end: 17
      range: [9, 17]
      loc:
        start:
          line: 1
          column: 9
        end:
          line: 1
          column: 17
    start: 0
    end: 17
    range: [0, 17]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 17

  testAstLocationData 'yes if maybe',
    type: 'IfStatement'
    test:
      start: 7
      end: 12
      range: [7, 12]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 12
    consequent:
      body: [
        expression:
          start: 0
          end: 3
          range: [0, 3]
          loc:
            start:
              line: 1
              column: 0
            end:
              line: 1
              column: 3
        start: 0
        end: 3
        range: [0, 3]
        loc:
          start:
            line: 1
            column: 0
          end:
            line: 1
            column: 3
      ]
      start: 0
      end: 3
      range: [0, 3]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 3
    start: 0
    end: 12
    range: [0, 12]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 12

  testAstLocationData 'unless x then x else if y then y else z',
    type: 'IfStatement'
    test:
      start: 7
      end: 8
      range: [7, 8]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 8
    consequent:
      body: [
        expression:
          start: 14
          end: 15
          range: [14, 15]
          loc:
            start:
              line: 1
              column: 14
            end:
              line: 1
              column: 15
        start: 14
        end: 15
        range: [14, 15]
        loc:
          start:
            line: 1
            column: 14
          end:
            line: 1
            column: 15
      ]
      start: 9
      end: 15
      range: [9, 15]
      loc:
        start:
          line: 1
          column: 9
        end:
          line: 1
          column: 15
    alternate:
      test:
        start: 24
        end: 25
        range: [24, 25]
        loc:
          start:
            line: 1
            column: 24
          end:
            line: 1
            column: 25
      consequent:
        body: [
          expression:
            start: 31
            end: 32
            range: [31, 32]
            loc:
              start:
                line: 1
                column: 31
              end:
                line: 1
                column: 32
          start: 31
          end: 32
          range: [31, 32]
          loc:
            start:
              line: 1
              column: 31
            end:
              line: 1
              column: 32
        ]
        start: 26
        end: 32
        range: [26, 32]
        loc:
          start:
            line: 1
            column: 26
          end:
            line: 1
            column: 32
      alternate:
        body: [
          expression:
            start: 38
            end: 39
            range: [38, 39]
            loc:
              start:
                line: 1
                column: 38
              end:
                line: 1
                column: 39
          start: 38
          end: 39
          range: [38, 39]
          loc:
            start:
              line: 1
              column: 38
            end:
              line: 1
              column: 39
        ]
        start: 37
        end: 39
        range: [37, 39]
        loc:
          start:
            line: 1
            column: 37
          end:
            line: 1
            column: 39
      start: 21
      end: 39
      range: [21, 39]
      loc:
        start:
          line: 1
          column: 21
        end:
          line: 1
          column: 39
    start: 0
    end: 39
    range: [0, 39]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 39

  testAstLocationData '''
    if a
      b
    else
      if c
        d
  ''',
    type: 'IfStatement'
    test:
      start: 3
      end: 4
      range: [3, 4]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 1
          column: 4
    consequent:
      body: [
        expression:
          start: 7
          end: 8
          range: [7, 8]
          loc:
            start:
              line: 2
              column: 2
            end:
              line: 2
              column: 3
        start: 7
        end: 8
        range: [7, 8]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 3
      ]
      start: 5
      end: 8
      range: [5, 8]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 2
          column: 3
    alternate:
      body: [
        test:
          start: 19
          end: 20
          range: [19, 20]
          loc:
            start:
              line: 4
              column: 5
            end:
              line: 4
              column: 6
        consequent:
          body: [
            expression:
              start: 25
              end: 26
              range: [25, 26]
              loc:
                start:
                  line: 5
                  column: 4
                end:
                  line: 5
                  column: 5
            start: 25
            end: 26
            range: [25, 26]
            loc:
              start:
                line: 5
                column: 4
              end:
                line: 5
                column: 5
          ]
          start: 21
          end: 26
          range: [21, 26]
          loc:
            start:
              line: 5
              column: 0
            end:
              line: 5
              column: 5
        start: 16
        end: 26
        range: [16, 26]
        loc:
          start:
            line: 4
            column: 2
          end:
            line: 5
            column: 5
      ]
      start: 14
      end: 26
      range: [14, 26]
      loc:
        start:
          line: 4
          column: 0
        end:
          line: 5
          column: 5
    start: 0
    end: 26
    range: [0, 26]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 5
        column: 5

  testAstLocationData '''
    a =
      if b then c else if d then e
  ''',
    type: 'AssignmentExpression'
    right:
      test:
        start: 9
        end: 10
        range: [9, 10]
        loc:
          start:
            line: 2
            column: 5
          end:
            line: 2
            column: 6
      consequent:
        start: 16
        end: 17
        range: [16, 17]
        loc:
          start:
            line: 2
            column: 12
          end:
            line: 2
            column: 13
      alternate:
        test:
          start: 26
          end: 27
          range: [26, 27]
          loc:
            start:
              line: 2
              column: 22
            end:
              line: 2
              column: 23
        consequent:
          start: 33
          end: 34
          range: [33, 34]
          loc:
            start:
              line: 2
              column: 29
            end:
              line: 2
              column: 30
        start: 23
        end: 34
        range: [23, 34]
        loc:
          start:
            line: 2
            column: 19
          end:
            line: 2
            column: 30
      start: 6
      end: 34
      range: [6, 34]
      loc:
        start:
          line: 2
          column: 2
        end:
          line: 2
          column: 30
    start: 0
    end: 34
    range: [0, 34]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 30

  testAstLocationData '''
    f(
      if b
        c
        d
    )
  ''',
    type: 'CallExpression'
    arguments: [
      test:
        start: 8
        end: 9
        range: [8, 9]
        loc:
          start:
            line: 2
            column: 5
          end:
            line: 2
            column: 6
      consequent:
        body: [
          expression:
            start: 14
            end: 15
            range: [14, 15]
            loc:
              start:
                line: 3
                column: 4
              end:
                line: 3
                column: 5
          start: 14
          end: 15
          range: [14, 15]
          loc:
            start:
              line: 3
              column: 4
            end:
              line: 3
              column: 5
        ,
          expression:
            start: 20
            end: 21
            range: [20, 21]
            loc:
              start:
                line: 4
                column: 4
              end:
                line: 4
                column: 5
          start: 20
          end: 21
          range: [20, 21]
          loc:
            start:
              line: 4
              column: 4
            end:
              line: 4
              column: 5
        ]
        start: 10
        end: 21
        range: [10, 21]
        loc:
          start:
            line: 3
            column: 0
          end:
            line: 4
            column: 5
    ]
    start: 0
    end: 23
    range: [0, 23]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 5
        column: 1

test "AST as expected for While node", ->
  testAstLocationData 'loop 1',
    type: 'WhileStatement'
    test:
      start: 0
      end: 4
      range: [0, 4]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 4
    body:
      body: [
        expression:
          start: 5
          end: 6
          range: [5, 6]
          loc:
            start:
              line: 1
              column: 5
            end:
              line: 1
              column: 6
        start: 5
        end: 6
        range: [5, 6]
        loc:
          start:
            line: 1
            column: 5
          end:
            line: 1
            column: 6
      ]
      start: 5
      end: 6
      range: [5, 6]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 6
    start: 0
    end: 6
    range: [0, 6]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 6

  testAstLocationData 'while 1 < 2 then',
    type: 'WhileStatement'
    test:
      start: 6
      end: 11
      range: [6, 11]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 11
    body:
      start: 12
      end: 16
      range: [12, 16]
      loc:
        start:
          line: 1
          column: 12
        end:
          line: 1
          column: 16
    start: 0
    end: 16
    range: [0, 16]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 16

  testAstLocationData 'while 1 < 2 then fn()',
    type: 'WhileStatement'
    test:
      start: 6
      end: 11
      range: [6, 11]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 11
    body:
      body: [
        expression:
          start: 17
          end: 21
          range: [17, 21]
          loc:
            start:
              line: 1
              column: 17
            end:
              line: 1
              column: 21
        start: 17
        end: 21
        range: [17, 21]
        loc:
          start:
            line: 1
            column: 17
          end:
            line: 1
            column: 21
      ]
      start: 12
      end: 21
      range: [12, 21]
      loc:
        start:
          line: 1
          column: 12
        end:
          line: 1
          column: 21
    start: 0
    end: 21
    range: [0, 21]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 21


  testAstLocationData '''
    x() until y
  ''',
    type: 'WhileStatement'
    test:
      start: 10
      end: 11
      range: [10, 11]
      loc:
        start:
          line: 1
          column: 10
        end:
          line: 1
          column: 11
    body:
      body: [
        expression:
          start: 0
          end: 3
          range: [0, 3]
          loc:
            start:
              line: 1
              column: 0
            end:
              line: 1
              column: 3
        start: 0
        end: 3
        range: [0, 3]
        loc:
          start:
            line: 1
            column: 0
          end:
            line: 1
            column: 3
      ]
      start: 0
      end: 3
      range: [0, 3]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 3
    start: 0
    end: 11
    range: [0, 11]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 11

  testAstLocationData '''
    until x when y
      z++
  ''',
    type: 'WhileStatement'
    test:
      start: 6
      end: 7
      range: [6, 7]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 7
    body:
      body: [
        expression:
          start: 17
          end: 20
          range: [17, 20]
          loc:
            start:
              line: 2
              column: 2
            end:
              line: 2
              column: 5
        start: 17
        end: 20
        range: [17, 20]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 5
      ]
      start: 15
      end: 20
      range: [15, 20]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 2
          column: 5
    guard:
      start: 13
      end: 14
      range: [13, 14]
      loc:
        start:
          line: 1
          column: 13
        end:
          line: 1
          column: 14
    start: 0
    end: 20
    range: [0, 20]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 5

  testAstLocationData '''
    x while y when z
  ''',
    type: 'WhileStatement'
    test:
      start: 8
      end: 9
      range: [8, 9]
      loc:
        start:
          line: 1
          column: 8
        end:
          line: 1
          column: 9
    body:
      body: [
        expression:
          start: 0
          end: 1
          range: [0, 1]
          loc:
            start:
              line: 1
              column: 0
            end:
              line: 1
              column: 1
        start: 0
        end: 1
        range: [0, 1]
        loc:
          start:
            line: 1
            column: 0
          end:
            line: 1
            column: 1
      ]
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    guard:
      start: 15
      end: 16
      range: [15, 16]
      loc:
        start:
          line: 1
          column: 15
        end:
          line: 1
          column: 16
    start: 0
    end: 16
    range: [0, 16]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 16

  testAstLocationData '''
    loop
      a()
      b++
  ''',
    type: 'WhileStatement'
    test:
      start: 0
      end: 4
      range: [0, 4]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 4
    body:
      body: [
        expression:
          start: 7
          end: 10
          range: [7, 10]
          loc:
            start:
              line: 2
              column: 2
            end:
              line: 2
              column: 5
        start: 7
        end: 10
        range: [7, 10]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 5
      ,
        expression:
          start: 13
          end: 16
          range: [13, 16]
          loc:
            start:
              line: 3
              column: 2
            end:
              line: 3
              column: 5
        start: 13
        end: 16
        range: [13, 16]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 5
      ]
      start: 5
      end: 16
      range: [5, 16]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 5
    start: 0
    end: 16
    range: [0, 16]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 5

test "AST location data as expected for `new.target` MetaProperty node", ->
  testAstLocationData '''
    -> new.target
  ''',
    type: 'FunctionExpression'
    body:
      body: [
        expression:
          meta:
            start: 3
            end: 6
            range: [3, 6]
            loc:
              start:
                line: 1
                column: 3
              end:
                line: 1
                column: 6
          property:
            start: 7
            end: 13
            range: [7, 13]
            loc:
              start:
                line: 1
                column: 7
              end:
                line: 1
                column: 13
          start: 3
          end: 13
          range: [3, 13]
          loc:
            start:
              line: 1
              column: 3
            end:
              line: 1
              column: 13
      ]
    start: 0
    end: 13
    range: [0, 13]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 13

test "AST location data as expected for `import.meta` MetaProperty node", ->
  testAstLocationData '''
    import.meta
  ''',
    type: 'MetaProperty'
    meta:
      start: 0
      end: 6
      range: [0, 6]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 6
    property:
      start: 7
      end: 11
      range: [7, 11]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 11
    start: 0
    end: 11
    range: [0, 11]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 11

test "AST location data as expected for For node", ->
  testAstLocationData 'for x, i in arr when x? then return',
    type: 'For'
    name:
      start: 4
      end: 5
      range: [4, 5]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 5
    index:
      start: 7
      end: 8
      range: [7, 8]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 8
    guard:
      start: 21
      end: 23
      range: [21, 23]
      loc:
        start:
          line: 1
          column: 21
        end:
          line: 1
          column: 23
    source:
      start: 12
      end: 15
      range: [12, 15]
      loc:
        start:
          line: 1
          column: 12
        end:
          line: 1
          column: 15
    body:
      body: [
        start: 29
        end: 35
        range: [29, 35]
        loc:
          start:
            line: 1
            column: 29
          end:
            line: 1
            column: 35
      ]
      start: 24
      end: 35
      range: [24, 35]
      loc:
        start:
          line: 1
          column: 24
        end:
          line: 1
          column: 35
    start: 0
    end: 35
    range: [0, 35]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 35

  testAstLocationData 'a = (x for x in y)',
    type: 'AssignmentExpression'
    right:
      name:
        start: 11
        end: 12
        range: [11, 12]
        loc:
          start:
            line: 1
            column: 11
          end:
            line: 1
            column: 12
      body:
        start: 5
        end: 6
        range: [5, 6]
        loc:
          start:
            line: 1
            column: 5
          end:
            line: 1
            column: 6
      source:
        start: 16
        end: 17
        range: [16, 17]
        loc:
          start:
            line: 1
            column: 16
          end:
            line: 1
            column: 17
      start: 5
      end: 17
      range: [5, 17]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 17
    start: 0
    end: 18
    range: [0, 18]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 18

  testAstLocationData 'x for [0...1]',
    type: 'For'
    body:
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    source:
      start: 6
      end: 13
      range: [6, 13]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 13
    start: 0
    end: 13
    range: [0, 13]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 13

  testAstLocationData '''
    for own x, y of z
      c()
      d
  ''',
    type: 'For'
    name:
      start: 11
      end: 12
      range: [11, 12]
      loc:
        start:
          line: 1
          column: 11
        end:
          line: 1
          column: 12
    index:
      start: 8
      end: 9
      range: [8, 9]
      loc:
        start:
          line: 1
          column: 8
        end:
          line: 1
          column: 9
    body:
      body: [
        start: 20
        end: 23
        range: [20, 23]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 5
      ,
        start: 26
        end: 27
        range: [26, 27]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 3
      ]
      start: 18
      end: 27
      range: [18, 27]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 3
    source:
      start: 16
      end: 17
      range: [16, 17]
      loc:
        start:
          line: 1
          column: 16
        end:
          line: 1
          column: 17
    start: 0
    end: 27
    range: [0, 27]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 3

  testAstLocationData '''
    ->
      for await x from y
        z
  ''',
    type: 'FunctionExpression'
    body:
      body: [
        name:
          start: 15
          end: 16
          range: [15, 16]
          loc:
            start:
              line: 2
              column: 12
            end:
              line: 2
              column: 13
        body:
          body: [
            start: 28
            end: 29
            range: [28, 29]
            loc:
              start:
                line: 3
                column: 4
              end:
                line: 3
                column: 5
          ]
          start: 24
          end: 29
          range: [24, 29]
          loc:
            start:
              line: 3
              column: 0
            end:
              line: 3
              column: 5
        source:
          start: 22
          end: 23
          range: [22, 23]
          loc:
            start:
              line: 2
              column: 19
            end:
              line: 2
              column: 20
        start: 5
        end: 29
        range: [5, 29]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 3
            column: 5
      ]
      start: 3
      end: 29
      range: [3, 29]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 5
    start: 0
    end: 29
    range: [0, 29]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 5

  testAstLocationData '''
    for {x} in y
      z
  ''',
    type: 'For'
    name:
      properties: [
        start: 5
        end: 6
        range: [5, 6]
        loc:
          start:
            line: 1
            column: 5
          end:
            line: 1
            column: 6
      ]
      start: 4
      end: 7
      range: [4, 7]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 7
    body:
      body: [
        start: 15
        end: 16
        range: [15, 16]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 3
      ]
      start: 13
      end: 16
      range: [13, 16]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 2
          column: 3
    source:
      start: 11
      end: 12
      range: [11, 12]
      loc:
        start:
          line: 1
          column: 11
        end:
          line: 1
          column: 12
    start: 0
    end: 16
    range: [0, 16]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 3

test "AST location data as expected for StringWithInterpolations node", ->
  testAstLocationData '"a#{b}c"',
    type: 'TemplateLiteral'
    expressions: [
      start: 4
      end: 5
      range: [4, 5]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 5
    ]
    quasis: [
      start: 1
      end: 2
      range: [1, 2]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 2
    ,
      start: 6
      end: 7
      range: [6, 7]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 7
    ]
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 8

  testAstLocationData '"""a#{b}c"""',
    type: 'TemplateLiteral'
    expressions: [
      start: 6
      end: 7
      range: [6, 7]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 7
    ]
    quasis: [
      start: 3
      end: 4
      range: [3, 4]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 1
          column: 4
    ,
      start: 8
      end: 9
      range: [8, 9]
      loc:
        start:
          line: 1
          column: 8
        end:
          line: 1
          column: 9
    ]
    start: 0
    end: 12
    range: [0, 12]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 12

  testAstLocationData '"#{b}"',
    type: 'TemplateLiteral'
    expressions: [
      start: 3
      end: 4
      range: [3, 4]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 1
          column: 4
    ]
    quasis: [
      start: 1
      end: 1
      range: [1, 1]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 1
    ,
      start: 5
      end: 5
      range: [5, 5]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 5
    ]
    start: 0
    end: 6
    range: [0, 6]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 6

  testAstLocationData '''
    " a
      #{b}
      c
    "
  ''',
    type: 'TemplateLiteral'
    expressions: [
      start: 8
      end: 9
      range: [8, 9]
      loc:
        start:
          line: 2
          column: 4
        end:
          line: 2
          column: 5
    ]
    quasis: [
      start: 1
      end: 6
      range: [1, 6]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 2
          column: 2
    ,
      start: 10
      end: 15
      range: [10, 15]
      loc:
        start:
          line: 2
          column: 6
        end:
          line: 4
          column: 0
    ]
    start: 0
    end: 16
    range: [0, 16]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 1

  testAstLocationData '''
    """
      a
        b#{
        c
      }d
    """
  ''',
    type: 'TemplateLiteral'
    expressions: [
      start: 20
      end: 21
      range: [20, 21]
      loc:
        start:
          line: 4
          column: 4
        end:
          line: 4
          column: 5
    ]
    quasis: [
      start: 3
      end: 13
      range: [3, 13]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 3
          column: 5
    ,
      start: 25
      end: 27
      range: [25, 27]
      loc:
        start:
          line: 5
          column: 3
        end:
          line: 6
          column: 0
    ]
    start: 0
    end: 30
    range: [0, 30]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 6
        column: 3

  # empty interpolation
  testAstLocationData '"#{}"',
    type: 'TemplateLiteral'
    expressions: [
      start: 3
      end: 3
      range: [3, 3]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 1
          column: 3
    ]
    quasis: [
      start: 1
      end: 1
      range: [1, 1]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 1
    ,
      start: 4
      end: 4
      range: [4, 4]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 4
    ]
    start: 0
    end: 5
    range: [0, 5]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 5

  testAstLocationData '''
    "#{
      # comment
     }"
    ''',
    type: 'TemplateLiteral'
    expressions: [
      start: 3
      end: 17
      range: [3, 17]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 3
          column: 1
    ]
    quasis: [
      start: 1
      end: 1
      range: [1, 1]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 1
    ,
      start: 18
      end: 18
      range: [18, 18]
      loc:
        start:
          line: 3
          column: 2
        end:
          line: 3
          column: 2
    ]
    start: 0
    end: 19
    range: [0, 19]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 3

  testAstLocationData '"#{ ### here ### }"',
    type: 'TemplateLiteral'
    expressions: [
      start: 3
      end: 17
      range: [3, 17]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 1
          column: 17
    ]
    quasis: [
      start: 1
      end: 1
      range: [1, 1]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 1
    ,
      start: 18
      end: 18
      range: [18, 18]
      loc:
        start:
          line: 1
          column: 18
        end:
          line: 1
          column: 18
    ]
    start: 0
    end: 19
    range: [0, 19]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 19

test "AST location data as expected for dynamic import", ->
  testAstLocationData '''
    import('a')
  ''',
    type: 'CallExpression'
    callee:
      start: 0
      end: 6
      range: [0, 6]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 6
    arguments: [
      start: 7
      end: 10
      range: [7, 10]
      loc:
        start:
          line: 1
          column: 7
        end:
          line: 1
          column: 10
    ]
    start: 0
    end: 11
    range: [0, 11]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 11

test "AST location data as expected for RegexWithInterpolations node", ->
  testAstLocationData '///^#{flavor}script$///',
    type: 'InterpolatedRegExpLiteral'
    interpolatedPattern:
      expressions: [
        start: 6
        end: 12
        range: [6, 12]
        loc:
          start:
            line: 1
            column: 6
          end:
            line: 1
            column: 12
      ]
      quasis: [
        start: 3
        end: 4
        range: [3, 4]
        loc:
          start:
            line: 1
            column: 3
          end:
            line: 1
            column: 4
      ,
        start: 13
        end: 20
        range: [13, 20]
        loc:
          start:
            line: 1
            column: 13
          end:
            line: 1
            column: 20
      ]
      start: 0
      end: 23
      range: [0, 23]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 23
    start: 0
    end: 23
    range: [0, 23]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 23

  testAstLocationData '''
    ///
      a
      #{b}///ig
  ''',
    type: 'InterpolatedRegExpLiteral'
    interpolatedPattern:
      expressions: [
        start: 12
        end: 13
        range: [12, 13]
        loc:
          start:
            line: 3
            column: 4
          end:
            line: 3
            column: 5
      ]
      quasis: [
        start: 3
        end: 10
        range: [3, 10]
        loc:
          start:
            line: 1
            column: 3
          end:
            line: 3
            column: 2
      ,
        start: 14
        end: 14
        range: [14, 14]
        loc:
          start:
            line: 3
            column: 6
          end:
            line: 3
            column: 6
      ]
      start: 0
      end: 17
      range: [0, 17]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 3
          column: 9
    start: 0
    end: 19
    range: [0, 19]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 11

  testAstLocationData '''
    ///
      a # first
      #{b} ### second ###
    ///ig
  ''',
    type: 'InterpolatedRegExpLiteral'
    comments: [
      start: 8
      end: 15
      range: [8, 15]
      loc:
        start:
          line: 2
          column: 4
        end:
          line: 2
          column: 11
    ,
      start: 23
      end: 37
      range: [23, 37]
      loc:
        start:
          line: 3
          column: 7
        end:
          line: 3
          column: 21
    ]

test "AST location data as expected for RegexLiteral node", ->
  testAstLocationData '/a/ig',
    type: 'RegExpLiteral'
    start: 0
    end: 5
    range: [0, 5]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 5

  testAstLocationData '''
    ///
      a
    ///i
  ''',
    type: 'RegExpLiteral'
    start: 0
    end: 12
    range: [0, 12]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 4

  testAstLocationData '/a\\w\\u1111\\u{11111}/',
    type: 'RegExpLiteral'
    start: 0
    end: 20
    range: [0, 20]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 20

  testAstLocationData '''
    ///
      a
      \\w\\u1111\\u{11111}
    ///
  ''',
    type: 'RegExpLiteral'
    start: 0
    end: 31
    range: [0, 31]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 3

  testAstLocationData '''
    ///
      /
      (.+)
      /
    ///
  ''',
    type: 'RegExpLiteral'
    start: 0
    end: 22
    range: [0, 22]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 5
        column: 3

  testAstLocationData '''
    ///
      a # first
      b ### second ###
    ///
  ''',
    type: 'RegExpLiteral'
    comments: [
      start: 8
      end: 15
      range: [8, 15]
      loc:
        start:
          line: 2
          column: 4
        end:
          line: 2
          column: 11
    ,
      start: 20
      end: 34
      range: [20, 34]
      loc:
        start:
          line: 3
          column: 4
        end:
          line: 3
          column: 18
    ]
    start: 0
    end: 38
    range: [0, 38]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 3

test "AST location data as expected for TaggedTemplateCall node", ->
  testAstLocationData 'func"tagged"',
    type: 'TaggedTemplateExpression'
    tag:
      start: 0
      end: 4
      range: [0, 4]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 4
    quasi:
      quasis: [
        start: 5
        end: 11
        range: [5, 11]
        loc:
          start:
            line: 1
            column: 5
          end:
            line: 1
            column: 11
      ]
      start: 4
      end: 12
      range: [4, 12]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 12
    start: 0
    end: 12
    range: [0, 12]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 12

  testAstLocationData 'a"b#{c}"',
    type: 'TaggedTemplateExpression'
    tag:
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    quasi:
      expressions: [
        start: 5
        end: 6
        range: [5, 6]
        loc:
          start:
            line: 1
            column: 5
          end:
            line: 1
            column: 6
      ]
      quasis: [
        start: 2
        end: 3
        range: [2, 3]
        loc:
          start:
            line: 1
            column: 2
          end:
            line: 1
            column: 3
      ,
        start: 7
        end: 7
        range: [7, 7]
        loc:
          start:
            line: 1
            column: 7
          end:
            line: 1
            column: 7
      ]
      start: 1
      end: 8
      range: [1, 8]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 8
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 8

  testAstLocationData '''
    a"""
      b#{c}
    """
  ''',
    type: 'TaggedTemplateExpression'
    tag:
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    quasi:
      expressions: [
        start: 10
        end: 11
        range: [10, 11]
        loc:
          start:
            line: 2
            column: 5
          end:
            line: 2
            column: 6
      ]
      quasis: [
        start: 4
        end: 8
        range: [4, 8]
        loc:
          start:
            line: 1
            column: 4
          end:
            line: 2
            column: 3
      ,
        start: 12
        end: 13
        range: [12, 13]
        loc:
          start:
            line: 2
            column: 7
          end:
            line: 3
            column: 0
      ]
      start: 1
      end: 16
      range: [1, 16]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 3
          column: 3
    start: 0
    end: 16
    range: [0, 16]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 3

  testAstLocationData """
    a'''
      b
    '''
  """,
    type: 'TaggedTemplateExpression'
    tag:
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    quasi:
      quasis: [
        start: 4
        end: 9
        range: [4, 9]
        loc:
          start:
            line: 1
            column: 4
          end:
            line: 3
            column: 0
      ]
      start: 1
      end: 12
      range: [1, 12]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 3
          column: 3
    start: 0
    end: 12
    range: [0, 12]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 3

test "AST location data as expected for Class node", ->
  testAstLocationData 'class Klass',
    type: 'ClassDeclaration'
    id:
      start: 6
      end: 11
      range: [6, 11]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 11
    body:
      start: 11
      end: 11
      range: [11, 11]
      loc:
        start:
          line: 1
          column: 11
        end:
          line: 1
          column: 11
    start: 0
    end: 11
    range: [0, 11]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 11

  testAstLocationData 'class child extends parent',
    type: 'ClassDeclaration'
    id:
      start: 6
      end: 11
      range: [6, 11]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 11
    superClass:
      start: 20
      end: 26
      range: [20, 26]
      loc:
        start:
          line: 1
          column: 20
        end:
          line: 1
          column: 26
    body:
      start: 26
      end: 26
      range: [26, 26]
      loc:
        start:
          line: 1
          column: 26
        end:
          line: 1
          column: 26
    start: 0
    end: 26
    range: [0, 26]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 26

  testAstLocationData 'class Klass then constructor: ->',
    type: 'ClassDeclaration'
    id:
      start: 6
      end: 11
      range: [6, 11]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 11
    body:
      body: [
        key:
          start: 17
          end: 28
          range: [17, 28]
          loc:
            start:
              line: 1
              column: 17
            end:
              line: 1
              column: 28
        start: 17
        end: 32
        range: [17, 32]
        loc:
          start:
            line: 1
            column: 17
          end:
            line: 1
            column: 32
      ]
      start: 12
      end: 32
      range: [12, 32]
      loc:
        start:
          line: 1
          column: 12
        end:
          line: 1
          column: 32
    start: 0
    end: 32
    range: [0, 32]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 32

  testAstLocationData '''
    a = class A
      b: ->
        c
  ''',
    type: 'AssignmentExpression'
    right:
      id:
        start: 10
        end: 11
        range: [10, 11]
        loc:
          start:
            line: 1
            column: 10
          end:
            line: 1
            column: 11
      body:
        body: [
          key:
            start: 14
            end: 15
            range: [14, 15]
            loc:
              start:
                line: 2
                column: 2
              end:
                line: 2
                column: 3
          body:
            body: [
              start: 24
              end: 25
              range: [24, 25]
              loc:
                start:
                  line: 3
                  column: 4
                end:
                  line: 3
                  column: 5
            ]
            start: 20
            end: 25
            range: [20, 25]
            loc:
              start:
                line: 3
                column: 0
              end:
                line: 3
                column: 5
          start: 14
          end: 25
          range: [14, 25]
          loc:
            start:
              line: 2
              column: 2
            end:
              line: 3
              column: 5
        ]
        start: 12
        end: 25
        range: [12, 25]
        loc:
          start:
            line: 2
            column: 0
          end:
            line: 3
            column: 5
      start: 4
      end: 25
      range: [4, 25]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 3
          column: 5
    start: 0
    end: 25
    range: [0, 25]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 5

  testAstLocationData '''
    class A
      @b: ->
      @c = ->
      @d: 1
      @e = 2
      A.f = 3
      A.g = ->
      this.h = ->
      this.i = 4
  ''',
    type: 'ClassDeclaration'
    id:
      start: 6
      end: 7
      range: [6, 7]
      loc:
        start:
          line: 1
          column: 6
        end:
          line: 1
          column: 7
    body:
      body: [
        key:
          start: 11
          end: 12
          range: [11, 12]
          loc:
            start:
              line: 2
              column: 3
            end:
              line: 2
              column: 4
        staticClassName:
          start: 10
          end: 11
          range: [10, 11]
          loc:
            start:
              line: 2
              column: 2
            end:
              line: 2
              column: 3
        start: 10
        end: 16
        range: [10, 16]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 8
      ,
        key:
          start: 20
          end: 21
          range: [20, 21]
          loc:
            start:
              line: 3
              column: 3
            end:
              line: 3
              column: 4
        staticClassName:
          start: 19
          end: 20
          range: [19, 20]
          loc:
            start:
              line: 3
              column: 2
            end:
              line: 3
              column: 3
        start: 19
        end: 26
        range: [19, 26]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 9
      ,
        key:
          start: 30
          end: 31
          range: [30, 31]
          loc:
            start:
              line: 4
              column: 3
            end:
              line: 4
              column: 4
        staticClassName:
          start: 29
          end: 30
          range: [29, 30]
          loc:
            start:
              line: 4
              column: 2
            end:
              line: 4
              column: 3
        value:
          start: 33
          end: 34
          range: [33, 34]
          loc:
            start:
              line: 4
              column: 6
            end:
              line: 4
              column: 7
        start: 29
        end: 34
        range: [29, 34]
        loc:
          start:
            line: 4
            column: 2
          end:
            line: 4
            column: 7
      ,
        key:
          start: 38
          end: 39
          range: [38, 39]
          loc:
            start:
              line: 5
              column: 3
            end:
              line: 5
              column: 4
        staticClassName:
          start: 37
          end: 38
          range: [37, 38]
          loc:
            start:
              line: 5
              column: 2
            end:
              line: 5
              column: 3
        value:
          start: 42
          end: 43
          range: [42, 43]
          loc:
            start:
              line: 5
              column: 7
            end:
              line: 5
              column: 8
        start: 37
        end: 43
        range: [37, 43]
        loc:
          start:
            line: 5
            column: 2
          end:
            line: 5
            column: 8
      ,
        key:
          start: 48
          end: 49
          range: [48, 49]
          loc:
            start:
              line: 6
              column: 4
            end:
              line: 6
              column: 5
        staticClassName:
          start: 46
          end: 47
          range: [46, 47]
          loc:
            start:
              line: 6
              column: 2
            end:
              line: 6
              column: 3
        value:
          start: 52
          end: 53
          range: [52, 53]
          loc:
            start:
              line: 6
              column: 8
            end:
              line: 6
              column: 9
        start: 46
        end: 53
        range: [46, 53]
        loc:
          start:
            line: 6
            column: 2
          end:
            line: 6
            column: 9
      ,
        key:
          start: 58
          end: 59
          range: [58, 59]
          loc:
            start:
              line: 7
              column: 4
            end:
              line: 7
              column: 5
        staticClassName:
          start: 56
          end: 57
          range: [56, 57]
          loc:
            start:
              line: 7
              column: 2
            end:
              line: 7
              column: 3
        start: 56
        end: 64
        range: [56, 64]
        loc:
          start:
            line: 7
            column: 2
          end:
            line: 7
            column: 10
      ,
        key:
          start: 72
          end: 73
          range: [72, 73]
          loc:
            start:
              line: 8
              column: 7
            end:
              line: 8
              column: 8
        staticClassName:
          start: 67
          end: 71
          range: [67, 71]
          loc:
            start:
              line: 8
              column: 2
            end:
              line: 8
              column: 6
        start: 67
        end: 78
        range: [67, 78]
        loc:
          start:
            line: 8
            column: 2
          end:
            line: 8
            column: 13
      ,
        key:
          start: 86
          end: 87
          range: [86, 87]
          loc:
            start:
              line: 9
              column: 7
            end:
              line: 9
              column: 8
        staticClassName:
          start: 81
          end: 85
          range: [81, 85]
          loc:
            start:
              line: 9
              column: 2
            end:
              line: 9
              column: 6
        value:
          start: 90
          end: 91
          range: [90, 91]
          loc:
            start:
              line: 9
              column: 11
            end:
              line: 9
              column: 12
        start: 81
        end: 91
        range: [81, 91]
        loc:
          start:
            line: 9
            column: 2
          end:
            line: 9
            column: 12
      ]
      start: 8
      end: 91
      range: [8, 91]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 9
          column: 12
    start: 0
    end: 91
    range: [0, 91]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 9
        column: 12

  testAstLocationData '''
    class A
      b: 1
      [c]: 2
  ''',
    type: 'ClassDeclaration'
    body:
      body: [
        key:
          start: 10
          end: 11
          range: [10, 11]
          loc:
            start:
              line: 2
              column: 2
            end:
              line: 2
              column: 3
        value:
          start: 13
          end: 14
          range: [13, 14]
          loc:
            start:
              line: 2
              column: 5
            end:
              line: 2
              column: 6
        start: 10
        end: 14
        range: [10, 14]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 6
      ,
        key:
          start: 18
          end: 19
          range: [18, 19]
          loc:
            start:
              line: 3
              column: 3
            end:
              line: 3
              column: 4
        value:
          start: 22
          end: 23
          range: [22, 23]
          loc:
            start:
              line: 3
              column: 7
            end:
              line: 3
              column: 8
        start: 17
        end: 23
        range: [17, 23]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 8
      ]
      start: 8
      end: 23
      range: [8, 23]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 8
    start: 0
    end: 23
    range: [0, 23]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 8

  testAstLocationData '''
    class A
      @[b]: 1
      @[c]: ->
  ''',
    type: 'ClassDeclaration'
    body:
      body: [
        key:
          start: 12
          end: 13
          range: [12, 13]
          loc:
            start:
              line: 2
              column: 4
            end:
              line: 2
              column: 5
        staticClassName:
          start: 10
          end: 11
          range: [10, 11]
          loc:
            start:
              line: 2
              column: 2
            end:
              line: 2
              column: 3
        value:
          start: 16
          end: 17
          range: [16, 17]
          loc:
            start:
              line: 2
              column: 8
            end:
              line: 2
              column: 9
        start: 10
        end: 17
        range: [10, 17]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 9
      ,
        key:
          start: 22
          end: 23
          range: [22, 23]
          loc:
            start:
              line: 3
              column: 4
            end:
              line: 3
              column: 5
        staticClassName:
          start: 20
          end: 21
          range: [20, 21]
          loc:
            start:
              line: 3
              column: 2
            end:
              line: 3
              column: 3
        start: 20
        end: 28
        range: [20, 28]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 10
      ]
      start: 8
      end: 28
      range: [8, 28]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 10
    start: 0
    end: 28
    range: [0, 28]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 10

  testAstLocationData '''
    class A
      b = 1
  ''',
    type: 'ClassDeclaration'
    body:
      body: [
        expression:
          left:
            start: 10
            end: 11
            range: [10, 11]
            loc:
              start:
                line: 2
                column: 2
              end:
                line: 2
                column: 3
          right:
            start: 14
            end: 15
            range: [14, 15]
            loc:
              start:
                line: 2
                column: 6
              end:
                line: 2
                column: 7
          start: 10
          end: 15
          range: [10, 15]
          loc:
            start:
              line: 2
              column: 2
            end:
              line: 2
              column: 7
        start: 10
        end: 15
        range: [10, 15]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 7
      ]
      start: 8
      end: 15
      range: [8, 15]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 2
          column: 7
    start: 0
    end: 15
    range: [0, 15]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 7

test "AST location data as expected for directives", ->
  testAstRootLocationData '''
    'directive 1'
    'use strict'
    f()
  ''',
    type: 'File'
    program:
      body: [
        start: 27
        end: 30
        range: [27, 30]
        loc:
          start:
            line: 3
            column: 0
          end:
            line: 3
            column: 3
      ]
      directives: [
        start: 0
        end: 13
        range: [0, 13]
        loc:
          start:
            line: 1
            column: 0
          end:
            line: 1
            column: 13
      ,
        start: 14
        end: 26
        range: [14, 26]
        loc:
          start:
            line: 2
            column: 0
          end:
            line: 2
            column: 12
      ]
      start: 0
      end: 30
      range: [0, 30]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 3
          column: 3
    start: 0
    end: 30
    range: [0, 30]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 3

  testAstRootLocationData '''
    'use strict'
  ''',
    type: 'File'
    program:
      directives: [
        start: 0
        end: 12
        range: [0, 12]
        loc:
          start:
            line: 1
            column: 0
          end:
            line: 1
            column: 12
      ]
      start: 0
      end: 12
      range: [0, 12]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 12
    start: 0
    end: 12
    range: [0, 12]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 12

  testAstLocationData '''
    ->
      'use strict'
      f()
      'not a directive'
      g
  ''',
    type: 'FunctionExpression'
    body:
      directives: [
        value:
          start: 5
          end: 17
          range: [5, 17]
          loc:
            start:
              line: 2
              column: 2
            end:
              line: 2
              column: 14
        start: 5
        end: 17
        range: [5, 17]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 14
      ]
      start: 3
      end: 47
      range: [3, 47]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 5
          column: 3
    start: 0
    end: 47
    range: [0, 47]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 5
        column: 3

  testAstLocationData '''
    class A
      'classes can have directives too'
      a: ->
  ''',
    type: 'ClassDeclaration'
    body:
      directives: [
        start: 10
        end: 43
        range: [10, 43]
        loc:
          start:
            line: 2
            column: 2
          end:
            line: 2
            column: 35
      ]
      start: 8
      end: 51
      range: [8, 51]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 7
    start: 0
    end: 51
    range: [0, 51]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 7

test "AST location data as expected for PassthroughLiteral node", ->
  testAstLocationData "`abc`",
    type: 'PassthroughLiteral'
    start: 0
    end: 5
    range: [0, 5]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 5

  code = '\nconst CONSTANT = "unreassignable!"\n'
  testAstLocationData """
    ```
      abc
    ```
  """,
    type: 'PassthroughLiteral'
    start: 0
    end: 13
    range: [0, 13]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 3

  testAstLocationData "``",
    type: 'PassthroughLiteral'
    start: 0
    end: 2
    range: [0, 2]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 2

test "AST location data as expected for comments", ->
  testAstCommentsLocationData '''
    a # simple line comment
  ''', [
    start: 2
    end: 23
    range: [2, 23]
    loc:
      start:
        line: 1
        column: 2
      end:
        line: 1
        column: 23
  ]

  testAstCommentsLocationData '''
    a ### simple here comment ###
  ''', [
    start: 2
    end: 29
    range: [2, 29]
    loc:
      start:
        line: 1
        column: 2
      end:
        line: 1
        column: 29
  ]

  testAstCommentsLocationData '''
    # just a line comment
  ''', [
    start: 0
    end: 21
    range: [0, 21]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 21
  ]

  testAstCommentsLocationData '''
    ### just a here comment ###
  ''', [
    start: 0
    end: 27
    range: [0, 27]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 27
  ]

  testAstCommentsLocationData '''
    "#{
      # empty interpolation line comment
     }"
  ''', [
    start: 6
    end: 40
    range: [6, 40]
    loc:
      start:
        line: 2
        column: 2
      end:
        line: 2
        column: 36
  ]

  testAstCommentsLocationData '''
    "#{
      ### empty interpolation block comment ###
     }"
  ''', [
    start: 6
    end: 47
    range: [6, 47]
    loc:
      start:
        line: 2
        column: 2
      end:
        line: 2
        column: 43
  ]

  testAstCommentsLocationData '''
    # multiple line comments
    # on consecutive lines
  ''', [
    start: 0
    end: 24
    range: [0, 24]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 24
  ,
    start: 25
    end: 47
    range: [25, 47]
    loc:
      start:
        line: 2
        column: 0
      end:
        line: 2
        column: 22
  ]

  testAstCommentsLocationData '''
    # multiple line comments

    # with blank line
  ''', [
    start: 0
    end: 24
    range: [0, 24]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 24
  ,
    start: 26
    end: 43
    range: [26, 43]
    loc:
      start:
        line: 3
        column: 0
      end:
        line: 3
        column: 17
  ]

  testAstCommentsLocationData '''
    #no whitespace line comment
  ''', [
    start: 0
    end: 27
    range: [0, 27]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 27
  ]

  testAstCommentsLocationData '''
    ###no whitespace here comment###
  ''', [
    start: 0
    end: 32
    range: [0, 32]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 32
  ]

  testAstCommentsLocationData '''
    ###
    # multiline
    # here comment
    ###
  ''', [
    start: 0
    end: 34
    range: [0, 34]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 3
  ]

  testAstCommentsLocationData '''
    if b
      ###
      # multiline
      # indented here comment
      ###
      c
  ''', [
    start: 7
    end: 56
    range: [7, 56]
    loc:
      start:
        line: 2
        column: 2
      end:
        line: 5
        column: 5
  ]

test "AST location data as expected for chained comparisons", ->
  testAstLocationData '''
    a >= b < c
  ''',
    type: 'ChainedComparison'
    operands: [
      start: 0
      end: 1
      range: [0, 1]
      loc:
        start:
          line: 1
          column: 0
        end:
          line: 1
          column: 1
    ,
      start: 5
      end: 6
      range: [5, 6]
      loc:
        start:
          line: 1
          column: 5
        end:
          line: 1
          column: 6
    ,
      start: 9
      end: 10
      range: [9, 10]
      loc:
        start:
          line: 1
          column: 9
        end:
          line: 1
          column: 10
    ]
    start: 0
    end: 10
    range: [0, 10]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 10

test "AST location data as expected for Sequence", ->
  testAstLocationData '''
    (a; b)
  ''',
    type: 'SequenceExpression'
    expressions: [
      start: 1
      end: 2
      range: [1, 2]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 2
    ,
      start: 4
      end: 5
      range: [4, 5]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 1
          column: 5
    ]
    start: 1
    end: 5
    range: [1, 5]
    loc:
      start:
        line: 1
        column: 1
      end:
        line: 1
        column: 5

  testAstLocationData '''
    (a; b)""
  ''',
    type: 'TaggedTemplateExpression'
    tag:
      expressions: [
        start: 1
        end: 2
        range: [1, 2]
        loc:
          start:
            line: 1
            column: 1
          end:
            line: 1
            column: 2
      ,
        start: 4
        end: 5
        range: [4, 5]
        loc:
          start:
            line: 1
            column: 4
          end:
            line: 1
            column: 5
      ]
      start: 1
      end: 5
      range: [1, 5]
      loc:
        start:
          line: 1
          column: 1
        end:
          line: 1
          column: 5
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 8

test "AST location data as expected for blocks with comments", ->
  # trailing indented comment
  testAstLocationData '''
    ->
      a
      # b
  ''',
    type: 'FunctionExpression'
    body:
      start: 3
      end: 12
      range: [3, 12]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 5
    start: 0
    end: 12
    range: [0, 12]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 5

  testAstLocationData '''
    if a
      b
      ### c ###
  ''',
    type: 'IfStatement'
    consequent:
      start: 5
      end: 20
      range: [5, 20]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 11
    start: 0
    end: 20
    range: [0, 20]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 11

  # trailing non-indented comment
  testAstLocationData '''
    ->
      a
    # b
  ''',
    type: 'FunctionExpression'
    body:
      start: 3
      end: 6
      range: [3, 6]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 2
          column: 3
    start: 0
    end: 6
    range: [0, 6]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 3

  testAstLocationData '''
    if a
      b
    ### c ###
  ''',
    type: 'IfStatement'
    consequent:
      start: 5
      end: 8
      range: [5, 8]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 2
          column: 3
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 3

  # multiple trailing indented comments
  testAstLocationData '''
    class A
      a: ->
      # b
      #comment
  ''',
    type: 'ClassDeclaration'
    body:
      start: 8
      end: 32
      range: [8, 32]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 4
          column: 10
    start: 0
    end: 32
    range: [0, 32]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 10

  testAstLocationData '''
    a = ->
      c
      # b
      ### comment ###
  ''',
    type: 'AssignmentExpression'
    right:
      start: 4
      end: 34
      range: [4, 34]
      loc:
        start:
          line: 1
          column: 4
        end:
          line: 4
          column: 17
    start: 0
    end: 34
    range: [0, 34]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 17

  # multiple trailing comments, some indented
  testAstLocationData '''
    class A
      a: ->
      # b
    #comment
  ''',
    type: 'ClassDeclaration'
    body:
      start: 8
      end: 21
      range: [8, 21]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 5
    start: 0
    end: 21
    range: [0, 21]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 5

  # leading indented comment
  testAstLocationData '''
    ->
      # a
      b
  ''',
    type: 'FunctionExpression'
    body:
      start: 3
      end: 12
      range: [3, 12]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 3
    start: 0
    end: 12
    range: [0, 12]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 3

  testAstLocationData '''
    if a
      ### b ###
      c
  ''',
    type: 'IfStatement'
    consequent:
      start: 5
      end: 20
      range: [5, 20]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 3
    start: 0
    end: 20
    range: [0, 20]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 3

  # multiple leading indented comments
  testAstLocationData '''
    ->
      # a
      # b
      c
  ''',
    type: 'FunctionExpression'
    body:
      start: 3
      end: 18
      range: [3, 18]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 4
          column: 3
    start: 0
    end: 18
    range: [0, 18]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 3

  testAstLocationData '''
    if a
      ### b ###
      # c
      d
  ''',
    type: 'IfStatement'
    consequent:
      start: 5
      end: 26
      range: [5, 26]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 4
          column: 3
    start: 0
    end: 26
    range: [0, 26]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 3

  # just a comment
  testAstLocationData '''
    ->
      # a
  ''',
    type: 'FunctionExpression'
    body:
      start: 3
      end: 8
      range: [3, 8]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 2
          column: 5
    start: 0
    end: 8
    range: [0, 8]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 2
        column: 5

  testAstLocationData '''
    if a
      ### b ###
    else
      c
  ''',
    type: 'IfStatement'
    consequent:
      start: 5
      end: 16
      range: [5, 16]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 2
          column: 11
    start: 0
    end: 25
    range: [0, 25]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 3

  # just a non-indented comment
  testAstLocationData '''
    ->
    # a
  ''',
    type: 'FunctionExpression'
    body:
      start: 2
      end: 2
      range: [2, 2]
      loc:
        start:
          line: 1
          column: 2
        end:
          line: 1
          column: 2
    start: 0
    end: 2
    range: [0, 2]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 1
        column: 2

  # nested dedented comment
  testAstLocationData '''
    switch a
      when b
        c
      # d
  ''',
    type: 'SwitchStatement'
    cases: [
      start: 11
      end: 23
      range: [11, 23]
      loc:
        start:
          line: 2
          column: 2
        end:
          line: 3
          column: 5
    ]
    start: 0
    end: 29
    range: [0, 29]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 5

  # trailing implicit call in condition followed by indented comment
  testAstLocationData '''
    if a b
      # c
      d
  ''',
    type: 'IfStatement'
    test:
      start: 3
      end: 6
      range: [3, 6]
      loc:
        start:
          line: 1
          column: 3
        end:
          line: 1
          column: 6
    consequent:
      start: 7
      end: 16
      range: [7, 16]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 3
          column: 3
    start: 0
    end: 16
    range: [0, 16]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 3

test "AST location data as expected for heregex comments", ->
  code = '''
    ///
      a # b
    ///
  '''

  testAstLocationData code,
    type: 'RegExpLiteral'
    start: 0
    end: 15
    range: [0, 15]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 3
        column: 3

  eq getAstRoot(code).comments.length, 0

test "AST location data as expected with carriage returns", ->
  code = '''
    a =\r
    "#{\r
      b\r
    }"
  '''

  testAstLocationData code,
    type: 'AssignmentExpression'
    right:
      expressions: [
        start: 12
        end: 13
        range: [12, 13]
        loc:
          start:
            line: 3
            column: 2
          end:
            line: 3
            column: 3
      ]
      quasis: [
        start: 6
        end: 6
        range: [6, 6]
        loc:
          start:
            line: 2
            column: 1
          end:
            line: 2
            column: 1
      ,
        start: 16
        end: 16
        range: [16, 16]
        loc:
          start:
            line: 4
            column: 1
          end:
            line: 4
            column: 1
      ]
      start: 5
      end: 17
      range: [5, 17]
      loc:
        start:
          line: 2
          column: 0
        end:
          line: 4
          column: 2
    start: 0
    end: 17
    range: [0, 17]
    loc:
      start:
        line: 1
        column: 0
      end:
        line: 4
        column: 2

</script>
<script type="text/x-coffeescript" class="test" id="argument_parsing">
return unless require?
{buildCSOptionParser} = require '../lib/coffeescript/command'

optionParser = buildCSOptionParser()

sameOptions = (opts1, opts2, msg) ->
  ownKeys = Object.keys(opts1).sort()
  otherKeys = Object.keys(opts2).sort()
  arrayEq ownKeys, otherKeys, msg
  for k in ownKeys
    arrayEq opts1[k], opts2[k], msg
  yes

test "combined options are not split after initial file name", ->
  argv = ['some-file.coffee', '-bc']
  parsed = optionParser.parse argv
  expected = arguments: ['some-file.coffee', '-bc']
  sameOptions parsed, expected

  argv = ['some-file.litcoffee', '-bc']
  parsed = optionParser.parse argv
  expected = arguments: ['some-file.litcoffee', '-bc']
  sameOptions parsed, expected

  argv = ['-c', 'some-file.coffee', '-bc']
  parsed = optionParser.parse argv
  expected =
    compile: yes
    arguments: ['some-file.coffee', '-bc']
  sameOptions parsed, expected

  argv = ['-bc', 'some-file.coffee', '-bc']
  parsed = optionParser.parse argv
  expected =
    bare: yes
    compile: yes
    arguments: ['some-file.coffee', '-bc']
  sameOptions parsed, expected

test "combined options are not split after a '--', which is discarded", ->
  argv = ['--', '-bc']
  parsed = optionParser.parse argv
  expected =
    doubleDashed: yes
    arguments: ['-bc']
  sameOptions parsed, expected

  argv = ['-bc', '--', '-bc']
  parsed = optionParser.parse argv
  expected =
    bare: yes
    compile: yes
    doubleDashed: yes
    arguments: ['-bc']
  sameOptions parsed, expected

test "options are not split after any '--'", ->
  argv = ['--', '--', '-bc']
  parsed = optionParser.parse argv
  expected =
    doubleDashed: yes
    arguments: ['--', '-bc']
  sameOptions parsed, expected

  argv = ['--', 'some-file.coffee', '--', 'arg']
  parsed = optionParser.parse argv
  expected =
    doubleDashed: yes
    arguments: ['some-file.coffee', '--', 'arg']
  sameOptions parsed, expected

  argv = ['--', 'arg', 'some-file.coffee', '--', '-bc']
  parsed = optionParser.parse argv
  expected =
    doubleDashed: yes
    arguments: ['arg', 'some-file.coffee', '--', '-bc']
  sameOptions parsed, expected

test "any non-option argument stops argument parsing", ->
  argv = ['arg', '-bc']
  parsed = optionParser.parse argv
  expected = arguments: ['arg', '-bc']
  sameOptions parsed, expected

test "later '--' are not removed", ->
  argv = ['some-file.coffee', '--', '-bc']
  parsed = optionParser.parse argv
  expected = arguments: ['some-file.coffee', '--', '-bc']
  sameOptions parsed, expected

test "throw on invalid options", ->
  argv = ['-k']
  throws -> optionParser.parse argv

  argv = ['-ck']
  throws (-> optionParser.parse argv), /multi-flag/

  argv = ['-kc']
  throws (-> optionParser.parse argv), /multi-flag/

  argv = ['-oc']
  throws (-> optionParser.parse argv), /needs an argument/

  argv = ['-o']
  throws (-> optionParser.parse argv), /value required/

  argv = ['-co']
  throws (-> optionParser.parse argv), /value required/

  # Check if all flags in a multi-flag are recognized before checking if flags
  # before the last need arguments.
  argv = ['-ok']
  throws (-> optionParser.parse argv), /unrecognized option/

test "has expected help text", ->
  ok optionParser.help() is '''

Usage: coffee [options] path/to/script.coffee [args]

If called without options, `coffee` will run your script.

      --ast          generate an abstract syntax tree of nodes
  -b, --bare         compile without a top-level function wrapper
  -c, --compile      compile to JavaScript and save as .js files
  -e, --eval         pass a string from the command line as input
  -h, --help         display this help message
  -i, --interactive  run an interactive CoffeeScript REPL
  -j, --join         concatenate the source CoffeeScript before compiling
  -l, --literate     treat stdio as literate style coffeescript
  -m, --map          generate source map and save as .js.map files
  -M, --inline-map   generate source map and include it directly in output
  -n, --nodes        print out the parse tree that the parser produces
      --nodejs       pass options directly to the "node" binary
      --no-header    suppress the "Generated by" header
  -o, --output       set the output path or path/filename for compiled JavaScript
  -p, --print        print out the compiled JavaScript
  -r, --require      require the given module before eval or REPL
  -s, --stdio        listen for and compile scripts over stdio
  -t, --transpile    pipe generated JavaScript through Babel
      --tokens       print out the tokens that the lexer/rewriter produce
  -v, --version      display the version number
  -w, --watch        watch scripts for changes and rerun commands

  '''

</script>
<script type="text/x-coffeescript" class="test" id="arrays">
# Array Literals
# --------------

# * Array Literals
# * Splats in Array Literals

# TODO: add indexing and method invocation tests: [1][0] is 1, [].toString()

test "trailing commas", ->
  trailingComma = [1, 2, 3,]
  ok (trailingComma[0] is 1) and (trailingComma[2] is 3) and (trailingComma.length is 3)

  trailingComma = [
    1, 2, 3,
    4, 5, 6
    7, 8, 9,
  ]
  (sum = (sum or 0) + n) for n in trailingComma

  a = [((x) -> x), ((x) -> x * x)]
  ok a.length is 2

test "incorrect indentation without commas", ->
  result = [['a']
   {b: 'c'}]
  ok result[0][0] is 'a'
  ok result[1]['b'] is 'c'

# Elisions
test "array elisions", ->
  eq [,1].length, 2
  eq [,,1,2,,].length, 5
  arr = [1,,2]
  eq arr.length, 3
  eq arr[1], undefined
  eq [,,].length, 2

test "array elisions indentation and commas", ->
  arr1 = [
    , 1, 2, , , 3,
    4, 5, 6
    , , 8, 9,
  ]
  eq arr1.length, 12
  eq arr1[5], 3
  eq arr1[9], undefined
  arr2 = [, , 1,
    2, , 3,
    , 4, 5
    6
    , , ,
  ]
  eq arr2.length, 12
  eq arr2[8], 5
  eq arr2[1], undefined

test "array elisions destructuring", ->
  arr = [1,2,3,4,5,6,7,8,9]
  [,a] = arr
  [,,,b] = arr
  arrayEq [a,b], [2,4]
  [,a,,b,,c,,,d] = arr
  arrayEq [a,b,c,d], [2,4,6,9]
  [
    ,e,
    ,f,
    ,g,
    ,,h] = arr
  arrayEq [e,f,g,h], [2,4,6,9]

test "array elisions destructuring with splats and expansions", ->
  arr = [1,2,3,4,5,6,7,8,9]
  [,a,,,b...] = arr
  arrayEq [a,b], [2,[5,6,7,8,9]]
  [,c,...,,d,,e] = arr
  arrayEq [c,d,e], [2,7,9]
  [...,f,,,g,,,] = arr
  arrayEq [f,g], [4,7]

test "array elisions as function parameters", ->
  arr = [1,2,3,4,5,6,7,8,9]
  foo = ([,a]) -> a
  a = foo arr
  eq a, 2
  foo = ([,,,a]) -> a
  a = foo arr
  eq a, 4
  foo = ([,a,,b,,c,,,d]) -> [a,b,c,d]
  [a,b,c,d] = foo arr
  arrayEq [a,b,c,d], [2,4,6,9]

test "array elisions nested destructuring", ->
  arr = [
    1,
    [2,3, [4,5,6, [7,8,9] ] ]
  ]
  [,a] = arr
  arrayEq a[2][3], [7,8,9]
  [,[,,[,b,,[,,c]]]] = arr
  eq b, 5
  eq c, 9
  aobj = [
    {},
    {x: 2},
    {},
    [
      {},
      {},
      {z:1, w:[1,2,4], p:3, q:4}
      {},
      {}
    ]
  ]
  [,d,,[,,{w}]] = aobj
  deepEqual d, {x:2}
  arrayEq w, [1,2,4]

test "#5112: array elisions not detected inside strings", ->
  arr = [
    str: ", #{3}"
  ]
  eq arr[0].str, ', 3'

# Splats in Array Literals

test "array splat expansions with assignments", ->
  nums = [1, 2, 3]
  list = [a = 0, nums..., b = 4]
  eq 0, a
  eq 4, b
  arrayEq [0,1,2,3,4], list


test "mixed shorthand objects in array lists", ->
  arr = [
    a:1
    'b'
    c:1
  ]
  ok arr.length is 3
  ok arr[2].c is 1

  arr = [b: 1, a: 2, 100]
  eq arr[1], 100

  arr = [a:0, b:1, (1 + 1)]
  eq arr[1], 2

  arr = [a:1, 'a', b:1, 'b']
  eq arr.length, 4
  eq arr[2].b, 1
  eq arr[3], 'b'

test "array splats with nested arrays", ->
  nonce = {}
  a = [nonce]
  list = [1, 2, a...]
  eq list[0], 1
  eq list[2], nonce

  a = [[nonce]]
  list = [1, 2, a...]
  arrayEq list, [1, 2, [nonce]]

test "#4260: splat after existential operator soak", ->
  a = {b: [3]}
  foo = (a) -> [a]
  arrayEq [a?.b...], [3]
  arrayEq [c?.b ? []...], []
  arrayEq [...a?.b], [3]
  arrayEq [...c?.b ? []], []
  arrayEq foo(a?.b...), [3]
  arrayEq foo(...a?.b), [3]
  arrayEq foo(c?.b ? []...), [undefined]
  arrayEq foo(...c?.b ? []), [undefined]
  e = yes
  f = null
  arrayEq [(a if e)?.b...], [3]
  arrayEq [(a if f)?.b ? []...], []
  arrayEq [...(a if e)?.b], [3]
  arrayEq [...(a if f)?.b ? []], []
  arrayEq foo((a if e)?.b...), [3]
  arrayEq foo(...(a if e)?.b), [3]
  arrayEq foo((a if f)?.b ? []...), [undefined]
  arrayEq foo(...(a if f)?.b ? []), [undefined]

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  arrayEq [... a?.b], [3]
  arrayEq [... c?.b ? []], []
  arrayEq [a?.b ...], [3]
  arrayEq [(a if e)?.b ...], [3]
  arrayEq foo(a?.b ...), [3]
  arrayEq foo(... a?.b), [3]

test "#1349: trailing if after splat", ->
  a = [3]
  b = yes
  c = null
  foo = (a) -> [a]
  arrayEq [a if b...], [3]
  arrayEq [(a if c) ? []...], []
  arrayEq [...a if b], [3]
  arrayEq [...(a if c) ? []], []
  arrayEq foo((a if b)...), [3]
  arrayEq foo(...(a if b)), [3]
  arrayEq foo((a if c) ? []...), [undefined]
  arrayEq foo(...(a if c) ? []), [undefined]

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  arrayEq [... a if b], [3]
  arrayEq [a if b ...], [3]

test "#1274: `[] = a()` compiles to `false` instead of `a()`", ->
  a = false
  fn = -> a = true
  [] = fn()
  ok a

test "#3194: string interpolation in array", ->
  arr = [ "a"
          key: 'value'
        ]
  eq 2, arr.length
  eq 'a', arr[0]
  eq 'value', arr[1].key

  b = 'b'
  arr = [ "a#{b}"
          key: 'value'
        ]
  eq 2, arr.length
  eq 'ab', arr[0]
  eq 'value', arr[1].key

test "regex interpolation in array", ->
  arr = [ /a/
          key: 'value'
        ]
  eq 2, arr.length
  eq 'a', arr[0].source
  eq 'value', arr[1].key

  b = 'b'
  arr = [ ///a#{b}///
          key: 'value'
        ]
  eq 2, arr.length
  eq 'ab', arr[0].source
  eq 'value', arr[1].key

test "splat extraction from generators", ->
  gen = ->
    yield 1
    yield 2
    yield 3
  arrayEq [ gen()... ], [ 1, 2, 3 ]

test "for-from loops over Array", ->
  array1 = [50, 30, 70, 20]
  array2 = []
  for x from array1
    array2.push(x)
  arrayEq array1, array2

  array1 = [[20, 30], [40, 50]]
  array2 = []
  for [a, b] from array1
    array2.push(b)
    array2.push(a)
  arrayEq array2, [30, 20, 50, 40]

  array1 = [{a: 10, b: 20, c: 30}, {a: 40, b: 50, c: 60}]
  array2 = []
  for {a: a, b, c: d} from array1
    array2.push([a, b, d])
  arrayEq array2, [[10, 20, 30], [40, 50, 60]]

  array1 = [[10, 20, 30, 40, 50]]
  for [a, b..., c] from array1
    eq 10, a
    arrayEq [20, 30, 40], b
    eq 50, c

test "for-from comprehensions over Array", ->
  array1 = (x + 10 for x from [10, 20, 30])
  ok array1.join(' ') is '20 30 40'

  array2 = (x for x from [30, 41, 57] when x %% 3 is 0)
  ok array2.join(' ') is '30 57'

  array1 = (b + 5 for [a, b] from [[20, 30], [40, 50]])
  ok array1.join(' ') is '35 55'

  array2 = (a + b for [a, b] from [[10, 20], [30, 40], [50, 60]] when a + b >= 70)
  ok array2.join(' ') is '70 110'

test "#5201: simple indented elisions", ->
  arr1 = [
    ,
    1,
    2,
    ,
    ,
    3,
    4,
    5,
    6
    ,
    ,
    8,
    9,
  ]
  eq arr1.length, 12
  eq arr1[5], 3
  eq arr1[9], undefined

  arr2 = [
    ,
    ,
    1,
    2,
    ,
    3,
    ,
    4,
    5
    6
    ,
    ,
    ,
  ]
  eq arr2.length, 12
  eq arr2[8], 5
  eq arr2[1], undefined

  arr3 = [
    ,
    ,
    ,
  ]
  eq arr3.length, 3

  arr4 = [, , ,]
  eq arr4.length, 3

</script>
<script type="text/x-coffeescript" class="test" id="assignment">
# Assignment
# ----------

# * Assignment
# * Compound Assignment
# * Destructuring Assignment
# * Context Property (@) Assignment
# * Existential Assignment (?=)
# * Assignment to variables similar to generated variables

test "context property assignment (using @)", ->
  nonce = {}
  addMethod = ->
    @method = -> nonce
    this
  eq nonce, addMethod.call({}).method()

test "unassignable values", ->
  nonce = {}
  for nonref in ['', '""', '0', 'f()'].concat CoffeeScript.RESERVED
    eq nonce, (try CoffeeScript.compile "#{nonref} = v" catch e then nonce)

# Compound Assignment

test "boolean operators", ->
  nonce = {}

  a  = 0
  a or= nonce
  eq nonce, a

  b  = 1
  b or= nonce
  eq 1, b

  c = 0
  c and= nonce
  eq 0, c

  d = 1
  d and= nonce
  eq nonce, d

  # ensure that RHS is treated as a group
  e = f = false
  e and= f or true
  eq false, e

test "compound assignment as a sub expression", ->
  [a, b, c] = [1, 2, 3]
  eq 6, (a + b += c)
  eq 1, a
  eq 5, b
  eq 3, c

# *note: this test could still use refactoring*
test "compound assignment should be careful about caching variables", ->
  count = 0
  list = []

  list[++count] or= 1
  eq 1, list[1]
  eq 1, count

  list[++count] ?= 2
  eq 2, list[2]
  eq 2, count

  list[count++] and= 6
  eq 6, list[2]
  eq 3, count

  base = ->
    ++count
    base

  base().four or= 4
  eq 4, base.four
  eq 4, count

  base().five ?= 5
  eq 5, base.five
  eq 5, count

  eq 5, base().five ?= 6
  eq 6, count

test "compound assignment with implicit objects", ->
  obj = undefined
  obj ?=
    one: 1

  eq 1, obj.one

  obj and=
    two: 2

  eq undefined, obj.one
  eq         2, obj.two

test "compound assignment (math operators)", ->
  num = 10
  num -= 5
  eq 5, num

  num *= 10
  eq 50, num

  num /= 10
  eq 5, num

  num %= 3
  eq 2, num

test "more compound assignment", ->
  a = {}
  val = undefined
  val ||= a
  val ||= true
  eq a, val

  b = {}
  val &&= true
  eq val, true
  val &&= b
  eq b, val

  c = {}
  val = null
  val ?= c
  val ?= true
  eq c, val

test "#1192: assignment starting with object literals", ->
  doesNotThrow (-> CoffeeScript.run "{}.p = 0")
  doesNotThrow (-> CoffeeScript.run "{}.p++")
  doesNotThrow (-> CoffeeScript.run "{}[0] = 1")
  doesNotThrow (-> CoffeeScript.run """{a: 1, 'b', "#{1}": 2}.p = 0""")
  doesNotThrow (-> CoffeeScript.run "{a:{0:{}}}.a[0] = 0")


# Destructuring Assignment

test "empty destructuring assignment", ->
  {} = {}
  [] = []

test "chained destructuring assignments", ->
  [a] = {0: b} = {'0': c} = [nonce={}]
  eq nonce, a
  eq nonce, b
  eq nonce, c

test "variable swapping to verify caching of RHS values when appropriate", ->
  a = nonceA = {}
  b = nonceB = {}
  c = nonceC = {}
  [a, b, c] = [b, c, a]
  eq nonceB, a
  eq nonceC, b
  eq nonceA, c
  [a, b, c] = [b, c, a]
  eq nonceC, a
  eq nonceA, b
  eq nonceB, c
  fn = ->
    [a, b, c] = [b, c, a]
  arrayEq [nonceA,nonceB,nonceC], fn()
  eq nonceA, a
  eq nonceB, b
  eq nonceC, c

test "#713: destructuring assignment should return right-hand-side value", ->
  nonces = [nonceA={},nonceB={}]
  eq nonces, [a, b] = [c, d] = nonces
  eq nonceA, a
  eq nonceA, c
  eq nonceB, b
  eq nonceB, d

test "#4787 destructuring of objects within arrays", ->
  arr = [1, {a:1, b:2}]
  [...,{a, b}] = arr
  eq a, 1
  eq b, arr[1].b
  deepEqual {a, b}, arr[1]

test "destructuring assignment with splats", ->
  a = {}; b = {}; c = {}; d = {}; e = {}
  [x,y...,z] = [a,b,c,d,e]
  eq a, x
  arrayEq [b,c,d], y
  eq e, z

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  [x,y ...,z] = [a,b,c,d,e]
  eq a, x
  arrayEq [b,c,d], y
  eq e, z

test "deep destructuring assignment with splats", ->
  a={}; b={}; c={}; d={}; e={}; f={}; g={}; h={}; i={}
  [u, [v, w..., x], y..., z] = [a, [b, c, d, e], f, g, h, i]
  eq a, u
  eq b, v
  arrayEq [c,d], w
  eq e, x
  arrayEq [f,g,h], y
  eq i, z

test "destructuring assignment with objects", ->
  a={}; b={}; c={}
  obj = {a,b,c}
  {a:x, b:y, c:z} = obj
  eq a, x
  eq b, y
  eq c, z

test "deep destructuring assignment with objects", ->
  a={}; b={}; c={}; d={}
  obj = {
    a
    b: {
      'c': {
        d: [
          b
          {e: c, f: d}
        ]
      }
    }
  }
  {a: w, 'b': {c: d: [x, {'f': z, e: y}]}} = obj
  eq a, w
  eq b, x
  eq c, y
  eq d, z

test "destructuring assignment with objects and splats", ->
  a={}; b={}; c={}; d={}
  obj = a: b: [a, b, c, d]
  {a: b: [y, z...]} = obj
  eq a, y
  arrayEq [b,c,d], z

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  {a: b: [y, z ...]} = obj
  eq a, y
  arrayEq [b,c,d], z

test "destructuring assignment against an expression", ->
  a={}; b={}
  [y, z] = if true then [a, b] else [b, a]
  eq a, y
  eq b, z

test "bracket insertion when necessary", ->
  [a] = [0] ? [1]
  eq a, 0

# for implicit destructuring assignment in comprehensions, see the comprehension tests

test "destructuring assignment with context (@) properties", ->
  a={}; b={}; c={}; d={}; e={}
  obj =
    fn: () ->
      local = [a, {b, c}, d, e]
      [@a, {b: @b, c: @c}, @d, @e] = local
  eq undefined, obj[key] for key in ['a','b','c','d','e']
  obj.fn()
  eq a, obj.a
  eq b, obj.b
  eq c, obj.c
  eq d, obj.d
  eq e, obj.e

test "#1024: destructure empty assignments to produce javascript-like results", ->
  eq 2 * [] = 3 + 5, 16

test "#1005: invalid identifiers allowed on LHS of destructuring assignment", ->
  disallowed = ['eval', 'arguments'].concat CoffeeScript.RESERVED
  throwsCompileError "[#{disallowed.join ', '}] = x", null, null, 'all disallowed'
  throwsCompileError "[#{disallowed.join '..., '}...] = x", null, null, 'all disallowed as splats'
  t = tSplat = null
  for v in disallowed when v isnt 'class' # `class` by itself is an expression
    throwsCompileError t, null, null, t = "[#{v}] = x"
    throwsCompileError tSplat, null, null, tSplat = "[#{v}...] = x"
  for v in disallowed
    doesNotThrowCompileError "[a.#{v}] = x"
    doesNotThrowCompileError "[a.#{v}...] = x"
    doesNotThrowCompileError "[@#{v}] = x"
    doesNotThrowCompileError "[@#{v}...] = x"

test "#2055: destructuring assignment with `new`", ->
  {length} = new Array
  eq 0, length

test "#156: destructuring with expansion", ->
  array = [1..5]
  [first, ..., last] = array
  eq 1, first
  eq 5, last
  [..., lastButOne, last] = array
  eq 4, lastButOne
  eq 5, last
  [first, second, ..., last] = array
  eq 2, second
  [..., last] = 'strings as well -> x'
  eq 'x', last
  throwsCompileError "[1, ..., 3]",        null, null, "prohibit expansion outside of assignment"
  throwsCompileError "[..., a, b...] = c", null, null, "prohibit expansion and a splat"
  throwsCompileError "[...] = c",          null, null, "prohibit lone expansion"

test "destructuring with dynamic keys", ->
  {"#{'a'}": a, """#{'b'}""": b, c} = {a: 1, b: 2, c: 3}
  eq 1, a
  eq 2, b
  eq 3, c
  throwsCompileError '{"#{a}"} = b'

test "simple array destructuring defaults", ->
  [a = 1] = []
  eq 1, a
  [a = 2] = [undefined]
  eq 2, a
  [a = 3] = [null]
  eq null, a # Breaking change in CS2: per ES2015, default values are applied for `undefined` but not for `null`.
  [a = 4] = [0]
  eq 0, a
  arr = [a = 5]
  eq 5, a
  arrayEq [5], arr

test "simple object destructuring defaults", ->
  {b = 1} = {}
  eq b, 1
  {b = 2} = {b: undefined}
  eq b, 2
  {b = 3} = {b: null}
  eq b, null # Breaking change in CS2: per ES2015, default values are applied for `undefined` but not for `null`.
  {b = 4} = {b: 0}
  eq b, 0

  {b: c = 1} = {}
  eq c, 1
  {b: c = 2} = {b: undefined}
  eq c, 2
  {b: c = 3} = {b: null}
  eq c, null # Breaking change in CS2: per ES2015, default values are applied for `undefined` but not for `null`.
  {b: c = 4} = {b: 0}
  eq c, 0

test "multiple array destructuring defaults", ->
  [a = 1, b = 2, c] = [undefined, 12, 13]
  eq a, 1
  eq b, 12
  eq c, 13
  [a, b = 2, c = 3] = [undefined, 12, 13]
  eq a, undefined
  eq b, 12
  eq c, 13
  [a = 1, b, c = 3] = [11, 12]
  eq a, 11
  eq b, 12
  eq c, 3

test "multiple object destructuring defaults", ->
  {a = 1, b: bb = 2, 'c': c = 3, "#{0}": d = 4} = {"#{'b'}": 12}
  eq a, 1
  eq bb, 12
  eq c, 3
  eq d, 4

test "array destructuring defaults with splats", ->
  [..., a = 9] = []
  eq a, 9
  [..., b = 9] = [19]
  eq b, 19

test "deep destructuring assignment with defaults", ->
  [a, [{b = 1, c = 3}] = [c: 2]] = [0]
  eq a, 0
  eq b, 1
  eq c, 2

test "destructuring assignment with context (@) properties and defaults", ->
  a={}; b={}; c={}; d={}; e={}
  obj =
    fn: () ->
      local = [a, {b, c: undefined}, d]
      [@a, {b: @b = b, @c = c}, @d, @e = e] = local
  eq undefined, obj[key] for key in ['a','b','c','d','e']
  obj.fn()
  eq a, obj.a
  eq b, obj.b
  eq c, obj.c
  eq d, obj.d
  eq e, obj.e

test "destructuring assignment with defaults single evaluation", ->
  callCount = 0
  fn = -> callCount++
  [a = fn()] = []
  eq 0, a
  eq 1, callCount
  [a = fn()] = [10]
  eq 10, a
  eq 1, callCount
  {a = fn(), b: c = fn()} = {a: 20, b: undefined}
  eq 20, a
  eq c, 1
  eq callCount, 2


# Existential Assignment

test "existential assignment", ->
  nonce = {}
  a = false
  a ?= nonce
  eq false, a
  b = undefined
  b ?= nonce
  eq nonce, b
  c = null
  c ?= nonce
  eq nonce, c

test "#1627: prohibit conditional assignment of undefined variables", ->
  throwsCompileError "x ?= 10",        null, null, "prohibit (x ?= 10)"
  throwsCompileError "x ||= 10",       null, null, "prohibit (x ||= 10)"
  throwsCompileError "x or= 10",       null, null, "prohibit (x or= 10)"
  throwsCompileError "do -> x ?= 10",  null, null, "prohibit (do -> x ?= 10)"
  throwsCompileError "do -> x ||= 10", null, null, "prohibit (do -> x ||= 10)"
  throwsCompileError "do -> x or= 10", null, null, "prohibit (do -> x or= 10)"
  doesNotThrowCompileError "x = null; x ?= 10",        null, "allow (x = null; x ?= 10)"
  doesNotThrowCompileError "x = null; x ||= 10",       null, "allow (x = null; x ||= 10)"
  doesNotThrowCompileError "x = null; x or= 10",       null, "allow (x = null; x or= 10)"
  doesNotThrowCompileError "x = null; do -> x ?= 10",  null, "allow (x = null; do -> x ?= 10)"
  doesNotThrowCompileError "x = null; do -> x ||= 10", null, "allow (x = null; do -> x ||= 10)"
  doesNotThrowCompileError "x = null; do -> x or= 10", null, "allow (x = null; do -> x or= 10)"

  throwsCompileError "-> -> -> x ?= 10", null, null, "prohibit (-> -> -> x ?= 10)"
  doesNotThrowCompileError "x = null; -> -> -> x ?= 10", null, "allow (x = null; -> -> -> x ?= 10)"

test "more existential assignment", ->
  global.temp ?= 0
  eq global.temp, 0
  global.temp or= 100
  eq global.temp, 100
  delete global.temp

test "#1348, #1216: existential assignment compilation", ->
  nonce = {}
  a = nonce
  b = (a ?= 0)
  eq nonce, b
  #the first ?= compiles into a statement; the second ?= compiles to a ternary expression
  eq a ?= b ?= 1, nonce

  if a then a ?= 2 else a = 3
  eq a, nonce

test "#1591, #1101: splatted expressions in destructuring assignment must be assignable", ->
  nonce = {}
  for nonref in ['', '""', '0', 'f()', '(->)'].concat CoffeeScript.RESERVED
    eq nonce, (try CoffeeScript.compile "[#{nonref}...] = v" catch e then nonce)

test "#1643: splatted accesses in destructuring assignments should not be declared as variables", ->
  nonce = {}
  accesses = ['o.a', 'o["a"]', '(o.a)', '(o.a).a', '@o.a', 'C::a', 'f().a', 'o?.a', 'o?.a.b', 'f?().a']
  for access in accesses
    for i,j in [1,2,3] #position can matter
      code =
        """
        nonce = {}; nonce2 = {}; nonce3 = {};
        @o = o = new (class C then a:{}); f = -> o
        [#{new Array(i).join('x,')}#{access}...] = [#{new Array(i).join('0,')}nonce, nonce2, nonce3]
        unless #{access}[0] is nonce and #{access}[1] is nonce2 and #{access}[2] is nonce3 then throw new Error('[...]')
        """
      eq nonce, unless (try CoffeeScript.run code, bare: true catch e then true) then nonce
  # subpatterns like `[[a]...]` and `[{a}...]`
  subpatterns = ['[sub, sub2, sub3]', '{0: sub, 1: sub2, 2: sub3}']
  for subpattern in subpatterns
    for i,j in [1,2,3]
      code =
        """
        nonce = {}; nonce2 = {}; nonce3 = {};
        [#{new Array(i).join('x,')}#{subpattern}...] = [#{new Array(i).join('0,')}nonce, nonce2, nonce3]
        unless sub is nonce and sub2 is nonce2 and sub3 is nonce3 then throw new Error('[sub...]')
        """
      eq nonce, unless (try CoffeeScript.run code, bare: true catch e then true) then nonce

test "#1838: Regression with variable assignment", ->
  name =
  'dave'

  eq name, 'dave'

test '#2211: splats in destructured parameters', ->
  doesNotThrowCompileError '([a...]) ->'
  doesNotThrowCompileError '([a...],b) ->'
  doesNotThrowCompileError '([a...],[b...]) ->'
  throwsCompileError '([a...,[a...]]) ->'
  doesNotThrowCompileError '([a...,[b...]]) ->'

test '#2213: invocations within destructured parameters', ->
  throwsCompileError '([a()])->'
  throwsCompileError '([a:b()])->'
  throwsCompileError '([a:b.c()])->'
  throwsCompileError '({a()})->'
  throwsCompileError '({a:b()})->'
  throwsCompileError '({a:b.c()})->'

test '#2532: compound assignment with terminator', ->
  doesNotThrowCompileError """
  a = "hello"
  a +=
  "
  world
  !
  "
  """

test "#2613: parens on LHS of destructuring", ->
  a = {}
  [(a).b] = [1, 2, 3]
  eq a.b, 1

test "#2181: conditional assignment as a subexpression", ->
  a = false
  false && a or= true
  eq false, a
  eq false, not a or= true

test "#1500: Assignment to variables similar to generated variables", ->
  len = 0
  x = ((results = null; n) for n in [1, 2, 3])
  arrayEq [1, 2, 3], x
  eq 0, len

  for x in [1, 2, 3]
    f = ->
      i = 0
    f()
    eq 'undefined', typeof i

  ref = 2
  x = ref * 2 ? 1
  eq x, 4
  eq 'undefined', typeof ref1

  x = {}
  base = -> x
  name = -1
  base()[-name] ?= 2
  eq x[1], 2
  eq base(), x
  eq name, -1

  f = (@a, a) -> [@a, a]
  arrayEq [1, 2], f.call scope = {}, 1, 2
  eq 1, scope.a

  try throw 'foo'
  catch error
    eq error, 'foo'

  eq error, 'foo'

  doesNotThrowCompileError '(@slice...) ->'

test "Assignment to variables similar to helper functions", ->
  f = (slice...) -> slice
  arrayEq [1, 2, 3], f 1, 2, 3
  eq 'undefined', typeof slice1

  class A
  class B extends A
    extend = 3
    hasProp = 4
    value: 5
    method: (bind, bind1) => [bind, bind1, extend, hasProp, @value]
  {method} = new B
  arrayEq [1, 2, 3, 4, 5], method 1, 2

  modulo = -1 %% 3
  eq 2, modulo

  indexOf = [1, 2, 3]
  ok 2 in indexOf

test "#4566: destructuring with nested default values", ->
  {a: {b = 1}} = a: {}
  eq 1, b

  {c: {d} = {}} = c: d: 3
  eq 3, d

  {e: {f = 5} = {}} = {}
  eq 5, f

test "#4878: Compile error when using destructuring with a splat or expansion in an array", ->
  arr = ['a', 'b', 'c', 'd']

  f1 = (list) ->
    [first, ..., last] = list

  f2 = (list) ->
    [first..., last] = list

  f3 = (list) ->
    ([first, ...] = list); first

  f4 = (list) ->
    ([first, rest...] = list); rest

  arrayEq f1(arr), arr
  arrayEq f2(arr), arr
  arrayEq f3(arr), 'a'
  arrayEq f4(arr), ['b', 'c', 'd']

  foo = (list) ->
    ret =
      if list?.length > 0
        [first, ..., last] = list
        [first, last]
      else
        []

  arrayEq foo(arr), ['a', 'd']

  bar = (list) ->
    ret =
      if list?.length > 0
        [first, rest...] = list
        [first, rest]
      else
        []

  arrayEq bar(arr), ['a', ['b', 'c', 'd']]

test "destructuring assignment with an empty array in object", ->
  obj =
    a1: [1, 2]
    b1: 3

  {a1:[], b1} = obj
  eq 'undefined', typeof a1
  eq b1, 3

  obj =
    a2:
      b2: [1, 2]
    c2: 3

  {a2: {b2:[]}, c2} = obj
  eq 'undefined', typeof b2
  eq c2, 3

test "#5004: array destructuring with accessors", ->
  obj =
    arr: ['a', 'b', 'c', 'd']
    list: {}
    f1: ->
      [@first, @rest...] = @arr
    f2: ->
      [@second, @third..., @last] = @rest
    f3: ->
      [@list.a, @list.middle..., @list.d] = @arr

  obj.f1()
  eq obj.first, 'a'
  arrayEq obj.rest, ['b', 'c', 'd']

  obj.f2()
  eq obj.second, 'b'
  arrayEq obj.third, ['c']
  eq obj.last, 'd'

  obj.f3()
  eq obj.list.a, 'a'
  arrayEq obj.list.middle, ['b', 'c']
  eq obj.list.d, 'd'

  [obj.list.middle..., d] = obj.arr
  eq d, 'd'
  arrayEq obj.list.middle, ['a', 'b', 'c']

test "#4884: destructured object splat", ->
  [{length}...] = [1, 2, 3]
  eq length, 3
  [{length: len}...] = [1, 2, 3]
  eq len, 3
  [{length}..., three] = [1, 2, 3]
  eq length, 2
  eq three, 3
  [{length: len}..., three] = [1, 2, 3]
  eq len, 2
  eq three, 3
  x = [{length}..., three] = [1, 2, 3]
  eq length, 2
  eq three, 3
  eq x[2], 3
  x = [{length: len}..., three] = [1, 2, 3]
  eq len, 2
  eq three, 3
  eq x[2], 3

test "#4884: destructured array splat", ->
  [[one, two, three]...] = [1, 2, 3]
  eq one, 1
  eq two, 2
  eq three, 3
  [[one, two]..., three] = [1, 2, 3]
  eq one, 1
  eq two, 2
  eq three, 3
  x = [[one, two]..., three] = [1, 2, 3]
  eq one, 1
  eq two, 2
  eq three, 3
  eq x[2], 3

</script>
<script type="text/x-coffeescript" class="test" id="async">
# Functions that contain the `await` keyword will compile into async functions,
# supported by Node 7.6+, Chrome 55+, Firefox 52+, Safari 10.1+ and Edge.
# But runtimes that don’t support the `await` keyword will throw an error just
# from parsing this file, even without executing it, even if we put
# `return unless try new Function 'async () => {}'` at the top of this file.
# Therefore we need to prevent runtimes which will choke on such code from
# parsing it, which is handled in `Cakefile`.


# This is always fulfilled.
winning = (val) -> Promise.resolve val

# This is always rejected.
failing = (val) -> Promise.reject new Error val


test "async as argument", ->
  ok ->
    await winning()

test "explicit async", ->
  a = do ->
    await return 5
  eq a.constructor, Promise
  a.then (val) ->
    eq val, 5

test "implicit async", ->
  a = do ->
    x = await winning(5)
    y = await winning(4)
    z = await winning(3)
    [x, y, z]

  eq a.constructor, Promise

test "async return value (implicit)", ->
  out = null
  a = ->
    x = await winning(5)
    y = await winning(4)
    z = await winning(3)
    [x, y, z]

  b = do ->
    out = await a()

  b.then ->
    arrayEq out, [5, 4, 3]

test "async return value (explicit)", ->
  out = null
  a = ->
    await return [5, 2, 3]

  b = do ->
    out = await a()

  b.then ->
    arrayEq out, [5, 2, 3]


test "async parameters", ->
  [out1, out2] = [null, null]
  a = (a, [b, c])->
    arr = [a]
    arr.push b
    arr.push c
    await return arr

  b = (a, b, c = 5)->
    arr = [a]
    arr.push b
    arr.push c
    await return arr

  c = do ->
    out1 = await a(5, [4, 3])
    out2 = await b(4, 4)

  c.then ->
    arrayEq out1, [5, 4, 3]
    arrayEq out2, [4, 4, 5]

test "async `this` scoping", ->
  bnd = null
  ubnd = null
  nst = null
  obj =
    bound: ->
      return do =>
        await return this
    unbound: ->
      return do ->
        await return this
    nested: ->
      return do =>
        await do =>
          await do =>
            await return this

  promise = do ->
    bnd = await obj.bound()
    ubnd = await obj.unbound()
    nst = await obj.nested()

  promise.then ->
    eq bnd, obj
    ok ubnd isnt obj
    eq nst, obj

test "await precedence", ->
  out = null

  fn = (win, fail) ->
    win(3)

  promise = do ->
    # assert precedence between unary (new) and power (**) operators
    out = 1 + await new Promise(fn) ** 2

  promise.then ->
    eq out, 10

test "`await` inside IIFEs", ->
  [x, y, z] = new Array(3)

  a = do ->
    x = switch (4)  # switch 4
      when 2
        await winning(1)
      when 4
        await winning(5)
      when 7
        await winning(2)

    y = try
      text = "this should be caught"
      throw new Error(text)
      await winning(1)
    catch e
      await winning(4)

    z = for i in [0..5]
      a = i * i
      await winning(a)

  a.then ->
    eq x, 5
    eq y, 4

    arrayEq z, [0, 1, 4, 9, 16, 25]

test "error handling", ->
  res = null
  val = 0
  a = ->
    try
      await failing("fail")
    catch e
      val = 7  # to assure the catch block runs
      return e

  b = do ->
    res = await a()

  b.then ->
    eq val, 7

    ok res.message?
    eq res.message, "fail"

test "await expression evaluates to argument if not A+", ->
  eq(await 4, 4)


test "implicit call with `await`", ->
  addOne = (arg) -> arg + 1

  a = addOne await 3
  eq a, 4

test "async methods in classes", ->
  class Base
    @static: ->
      await 1
    method: ->
      await 2

  eq await Base.static(), 1
  eq await new Base().method(), 2

  class Child extends Base
    @static: -> super()
    method: -> super()

  eq await Child.static(), 1
  eq await new Child().method(), 2

test "#3199: await multiline implicit object", ->
  do ->
    y =
      if no then await
        type: 'a'
        msg: 'b'
    eq undefined, y

test "top-level await", ->
  eqJS 'await null', 'await null;'

test "top-level wrapper has correct async attribute", ->
  starts = (code, prefix) ->
    compiled = CoffeeScript.compile code
    unless compiled.startsWith prefix
      fail """Expected generated JavaScript to start with:
        #{reset}#{prefix}#{red}
        but instead it was:
        #{reset}#{compiled}#{red}"""
  starts 'await null', '(async function'
  starts 'do -> await null', '(function'

</script>
<script type="text/x-coffeescript" class="test" id="async_iterators">
# This is always fulfilled.
winLater = (val, ms) ->
  new Promise (resolve) -> setTimeout (-> resolve val), ms

# This is always rejected.
failLater = (val, ms) ->
  new Promise (resolve, reject) -> setTimeout (-> reject new Error val), ms

createAsyncIterable = (syncIterable) ->
  for elem in syncIterable
    yield await winLater elem, 50

test "async iteration", ->
  foo = (x for await x from createAsyncIterable [1,2,3])
  arrayEq foo, [1, 2, 3]

test "async generator functions", ->
  foo = (val) ->
    yield await winLater val + 1, 50

  bar = (val) ->
    yield await failLater val - 1, 50

  a = await foo(41).next()
  eq a.value, 42

  try
    b = do -> await bar(41).next()
    b.catch (err) ->
      eq "40", err.message
  catch err
    ok no

</script>
<script type="text/x-coffeescript" class="test" id="booleans">
# Boolean Literals
# ----------------

# TODO: add method invocation tests: true.toString() is "true"

test "#764 Booleans should be indexable", ->
  toString = Boolean::toString

  eq toString, true['toString']
  eq toString, false['toString']
  eq toString, yes['toString']
  eq toString, no['toString']
  eq toString, on['toString']
  eq toString, off['toString']

  eq toString, true.toString
  eq toString, false.toString
  eq toString, yes.toString
  eq toString, no.toString
  eq toString, on.toString
  eq toString, off.toString

</script>
<script type="text/x-coffeescript" class="test" id="classes">
# Classes
# -------

# * Class Definition
# * Class Instantiation
# * Inheritance and Super
# * ES2015+ Class Interoperability

test "classes with a four-level inheritance chain", ->

  class Base
    func: (string) ->
      "zero/#{string}"

    @static: (string) ->
      "static/#{string}"

  class FirstChild extends Base
    func: (string) ->
      super('one/') + string

  SecondChild = class extends FirstChild
    func: (string) ->
      super('two/') + string

  thirdCtor = ->
    @array = [1, 2, 3]

  class ThirdChild extends SecondChild
    constructor: ->
      super()
      thirdCtor.call this

    # Gratuitous comment for testing.
    func: (string) ->
      super('three/') + string

  result = (new ThirdChild).func 'four'

  ok result is 'zero/one/two/three/four'
  ok Base.static('word') is 'static/word'

  ok (new ThirdChild).array.join(' ') is '1 2 3'


test "constructors with inheritance and super", ->

  identity = (f) -> f

  class TopClass
    constructor: (arg) ->
      @prop = 'top-' + arg

  class SuperClass extends TopClass
    constructor: (arg) ->
      identity super 'super-' + arg

  class SubClass extends SuperClass
    constructor: ->
      identity super 'sub'

  ok (new SubClass).prop is 'top-super-sub'


test "'super' with accessors", ->
  class Base
    m: -> 4
    n: -> 5
    o: -> 6

  name = 'o'
  class A extends Base
    m: -> super()
    n: -> super.n()
    "#{name}": -> super()
    p: -> super[name]()

  a = new A
  eq 4, a.m()
  eq 5, a.n()
  eq 6, a.o()
  eq 6, a.p()


test "soaked 'super' invocation", ->
  class Base
    method: -> 2

  class A extends Base
    method: -> super?()
    noMethod: -> super?()

  a = new A
  eq 2, a.method()
  eq undefined, a.noMethod()

  name = 'noMethod'
  class B extends Base
    "#{'method'}": -> super?()
    "#{'noMethod'}": -> super?() ? super['method']()

  b = new B
  eq 2, b.method()
  eq 2, b.noMethod()

test "'@' referring to the current instance, and not being coerced into a call", ->

  class ClassName
    amI: ->
      @ instanceof ClassName

  obj = new ClassName
  ok obj.amI()


test "super() calls in constructors of classes that are defined as object properties", ->

  class Hive
    constructor: (name) -> @name = name

  class Hive.Bee extends Hive
    constructor: (name) -> super name

  maya = new Hive.Bee 'Maya'
  ok maya.name is 'Maya'


test "classes with JS-keyword properties", ->

  class Class
    class: 'class'
    name: -> @class

  instance = new Class
  ok instance.class is 'class'
  ok instance.name() is 'class'


test "Classes with methods that are pre-bound to the instance, or statically, to the class", ->

  class Dog
    constructor: (name) ->
      @name = name

    bark: =>
      "#{@name} woofs!"

    @static = =>
      new this('Dog')

  spark = new Dog('Spark')
  fido  = new Dog('Fido')
  fido.bark = spark.bark

  ok fido.bark() is 'Spark woofs!'

  obj = func: Dog.static

  ok obj.func().name is 'Dog'


test "a bound function in a bound function", ->

  class Mini
    num: 10
    generate: =>
      for i in [1..3]
        =>
          @num

  m = new Mini
  eq (func() for func in m.generate()).join(' '), '10 10 10'


test "contructor called with varargs", ->

  class Connection
    constructor: (one, two, three) ->
      [@one, @two, @three] = [one, two, three]

    out: ->
      "#{@one}-#{@two}-#{@three}"

  list = [3, 2, 1]
  conn = new Connection list...
  ok conn instanceof Connection
  ok conn.out() is '3-2-1'


test "calling super and passing along all arguments", ->

  class Parent
    method: (args...) -> @args = args

  class Child extends Parent
    method: -> super arguments...

  c = new Child
  c.method 1, 2, 3, 4
  ok c.args.join(' ') is '1 2 3 4'


test "classes wrapped in decorators", ->

  func = (klass) ->
    klass::prop = 'value'
    klass

  func class Test
    prop2: 'value2'

  ok (new Test).prop  is 'value'
  ok (new Test).prop2 is 'value2'


test "anonymous classes", ->

  obj =
    klass: class
      method: -> 'value'

  instance = new obj.klass
  ok instance.method() is 'value'


test "Implicit objects as static properties", ->

  class Static
    @static =
      one: 1
      two: 2

  ok Static.static.one is 1
  ok Static.static.two is 2


test "nothing classes", ->

  c = class
  ok c instanceof Function


test "classes with static-level implicit objects", ->

  class A
    @static = one: 1
    two: 2

  class B
    @static = one: 1,
    two: 2

  eq A.static.one, 1
  eq A.static.two, undefined
  eq (new A).two, 2

  eq B.static.one, 1
  eq B.static.two, 2
  eq (new B).two, undefined


test "classes with value'd constructors", ->

  counter = 0
  classMaker = ->
    inner = ++counter
    ->
      @value = inner
      @

  class One
    constructor: classMaker()

  class Two
    constructor: classMaker()

  eq (new One).value, 1
  eq (new Two).value, 2
  eq (new One).value, 1
  eq (new Two).value, 2


test "executable class bodies", ->

  class A
    if true
      b: 'b'
    else
      c: 'c'

  a = new A

  eq a.b, 'b'
  eq a.c, undefined


test "#2502: parenthesizing inner object values", ->

  class A
    category:  (type: 'string')
    sections:  (type: 'number', default: 0)

  eq (new A).category.type, 'string'

  eq (new A).sections.default, 0


test "conditional prototype property assignment", ->
  debug = false

  class Person
    if debug
      age: -> 10
    else
      age: -> 20

  eq (new Person).age(), 20


test "mild metaprogramming", ->

  class Base
    @attr: (name) ->
      @::[name] = (val) ->
        if arguments.length > 0
          @["_#{name}"] = val
        else
          @["_#{name}"]

  class Robot extends Base
    @attr 'power'
    @attr 'speed'

  robby = new Robot

  ok robby.power() is undefined

  robby.power 11
  robby.speed Infinity

  eq robby.power(), 11
  eq robby.speed(), Infinity


test "namespaced classes do not reserve their function name in outside scope", ->

  one = {}
  two = {}

  class one.Klass
    @label = "one"

  class two.Klass
    @label = "two"

  eq typeof Klass, 'undefined'
  eq one.Klass.label, 'one'
  eq two.Klass.label, 'two'


test "nested classes", ->

  class Outer
    constructor: ->
      @label = 'outer'

    class @Inner
      constructor: ->
        @label = 'inner'

  eq (new Outer).label, 'outer'
  eq (new Outer.Inner).label, 'inner'


test "variables in constructor bodies are correctly scoped", ->

  class A
    x = 1
    constructor: ->
      x = 10
      y = 20
    y = 2
    captured: ->
      {x, y}

  a = new A
  eq a.captured().x, 10
  eq a.captured().y, 2


test "Issue #924: Static methods in nested classes", ->

  class A
    @B: class
      @c = -> 5

  eq A.B.c(), 5


test "`class extends this`", ->

  class A
    func: -> 'A'

  B = null
  makeClass = ->
    B = class extends this
      func: -> super() + ' B'

  makeClass.call A

  eq (new B()).func(), 'A B'


test "ensure that constructors invoked with splats return a new object", ->

  args = [1, 2, 3]
  Type = (@args) ->
  type = new Type args

  ok type and type instanceof Type
  ok type.args and type.args instanceof Array
  ok v is args[i] for v, i in type.args

  Type1 = (@a, @b, @c) ->
  type1 = new Type1 args...

  ok type1 instanceof   Type1
  eq type1.constructor, Type1
  ok type1.a is args[0] and type1.b is args[1] and type1.c is args[2]

  # Ensure that constructors invoked with splats cache the function.
  called = 0
  get = -> if called++ then false else class Type
  new (get()) args...

test "`new` shouldn't add extra parens", ->

  ok new Date().constructor is Date


test "`new` works against bare function", ->

  eq Date, new ->
    Date

test "`new` works against statement", ->

  class A
  (new try A) instanceof A

test "#1182: a subclass should be able to set its constructor to an external function", ->
  ctor = ->
    @val = 1
    return
  class A
  class B extends A
    constructor: ctor
  eq (new B).val, 1

test "#1182: external constructors continued", ->
  ctor = ->
  class A
  class B extends A
    method: ->
    constructor: ctor
  ok B::method

test "#1313: misplaced __extends", ->
  nonce = {}
  class A
  class B extends A
    prop: nonce
    constructor: -> super()
  eq nonce, B::prop

test "#1182: execution order needs to be considered as well", ->
  counter = 0
  makeFn = (n) -> eq n, ++counter; ->
  class B extends (makeFn 1)
    @B: makeFn 2
    constructor: makeFn 3

test "#1182: external constructors with bound functions", ->
  fn = ->
    {one: 1}
    this
  class B
  class A
    constructor: fn
    method: => this instanceof A
  ok (new A).method.call(new B)

test "#1372: bound class methods with reserved names", ->
  class C
    delete: =>
  ok C::delete

test "#1380: `super` with reserved names", ->
  class C
    do: -> super()
  ok C::do

  class B
    0: -> super()
  ok B::[0]

test "#1464: bound class methods should keep context", ->
  nonce  = {}
  nonce2 = {}
  class C
    constructor: (@id) ->
    @boundStaticColon: => new this(nonce)
    @boundStaticEqual= => new this(nonce2)
  eq nonce,  C.boundStaticColon().id
  eq nonce2, C.boundStaticEqual().id

test "#1009: classes with reserved words as determined names", -> (->
  eq 'function', typeof (class @for)
  ok not /\beval\b/.test (class @eval).toString()
  ok not /\barguments\b/.test (class @arguments).toString()
).call {}

test "#1482: classes can extend expressions", ->
  id = (x) -> x
  nonce = {}
  class A then nonce: nonce
  class B extends id A
  eq nonce, (new B).nonce

test "#1598: super works for static methods too", ->

  class Parent
    method: ->
      'NO'
    @method: ->
      'yes'

  class Child extends Parent
    @method: ->
      'pass? ' + super()

  eq Child.method(), 'pass? yes'

test "#1842: Regression with bound functions within bound class methods", ->

  class Store
    @bound: =>
      do =>
        eq this, Store

  Store.bound()

  # And a fancier case:

  class Store

    eq this, Store

    @bound: =>
      do =>
        eq this, Store

    @unbound: ->
      eq this, Store

    instance: =>
      ok this instanceof Store

  Store.bound()
  Store.unbound()
  (new Store).instance()

test "#1876: Class @A extends A", ->
  class A
  class @A extends A

  ok (new @A) instanceof A

test "#1813: Passing class definitions as expressions", ->
  ident = (x) -> x

  result = ident class A then x = 1

  eq result, A

  result = ident class B extends A
    x = 1

  eq result, B

test "#1966: external constructors should produce their return value", ->
  ctor = -> {}
  class A then constructor: ctor
  ok (new A) not instanceof A

test "#1980: regression with an inherited class with static function members", ->

  class A

  class B extends A
    @static: => 'value'

  eq B.static(), 'value'

test "#1534: class then 'use strict'", ->
  # [14.1 Directive Prologues and the Use Strict Directive](http://es5.github.com/#x14.1)
  nonce = {}
  error = 'do -> ok this'
  strictTest = "do ->'use strict';#{error}"
  return unless (try CoffeeScript.run strictTest, bare: yes catch e then nonce) is nonce

  throws -> CoffeeScript.run "class then 'use strict';#{error}", bare: yes
  doesNotThrow -> CoffeeScript.run "class then #{error}", bare: yes
  doesNotThrow -> CoffeeScript.run "class then #{error};'use strict'", bare: yes

  # comments are ignored in the Directive Prologue
  comments = ["""
  class
    ### comment ###
    'use strict'
    #{error}""",
  """
  class
    ### comment 1 ###
    ### comment 2 ###
    'use strict'
    #{error}""",
  """
  class
    ### comment 1 ###
    ### comment 2 ###
    'use strict'
    #{error}
    ### comment 3 ###"""
  ]
  throws (-> CoffeeScript.run comment, bare: yes) for comment in comments

  # [ES5 §14.1](http://es5.github.com/#x14.1) allows for other directives
  directives = ["""
  class
    'directive 1'
    'use strict'
    #{error}""",
  """
  class
    'use strict'
    'directive 2'
    #{error}""",
  """
  class
    ### comment 1 ###
    'directive 1'
    'use strict'
    #{error}""",
  """
  class
    ### comment 1 ###
    'directive 1'
    ### comment 2 ###
    'use strict'
    #{error}"""
  ]
  throws (-> CoffeeScript.run directive, bare: yes) for directive in directives

test "#2052: classes should work in strict mode", ->
  try
    do ->
      'use strict'
      class A
  catch e
    ok no

test "directives in class with extends ", ->
  strictTest = """
    class extends Object
      ### comment ###
      'use strict'
      do -> eq this, undefined
  """
  CoffeeScript.run strictTest, bare: yes

test "#2630: class bodies can't reference arguments", ->
  throwsCompileError 'class Test then arguments'

  # #4320: Don't be too eager when checking, though.
  class Test
    arguments: 5
  eq 5, Test::arguments

test "#2319: fn class n extends o.p [INDENT] x = 123", ->
  first = ->

  base = onebase: ->

  first class OneKeeper extends base.onebase
    one = 1
    one: -> one

  eq new OneKeeper().one(), 1


test "#2599: other typed constructors should be inherited", ->
  class Base
    constructor: -> return {}

  class Derived extends Base

  ok (new Derived) not instanceof Derived
  ok (new Derived) not instanceof Base
  ok (new Base) not instanceof Base

test "extending native objects works with and without defining a constructor", ->
  class MyArray extends Array
    method: -> 'yes!'

  myArray = new MyArray
  ok myArray instanceof MyArray
  ok 'yes!', myArray.method()

  class OverrideArray extends Array
    constructor: -> super()
    method: -> 'yes!'

  overrideArray = new OverrideArray
  ok overrideArray instanceof OverrideArray
  eq 'yes!', overrideArray.method()


test "#2782: non-alphanumeric-named bound functions", ->
  class A
    'b:c': =>
      'd'

  eq (new A)['b:c'](), 'd'


test "#2781: overriding bound functions", ->
  class A
    a: ->
        @b()
    b: =>
        1

  class B extends A
    b: =>
        2

  b = (new A).b
  eq b(), 1

  b = (new B).b
  eq b(), 2


test "#2791: bound function with destructured argument", ->
  class Foo
    method: ({a}) => 'Bar'

  eq (new Foo).method({a: 'Bar'}), 'Bar'


test "#2796: ditto, ditto, ditto", ->
  answer = null

  outsideMethod = (func) ->
    func.call message: 'wrong!'

  class Base
    constructor: ->
      @message = 'right!'
      outsideMethod @echo

    echo: =>
      answer = @message

  new Base
  eq answer, 'right!'

test "#3063: Class bodies cannot contain pure statements", ->
  throwsCompileError """
    class extends S
      return if S.f
      @f: => this
  """

test "#2949: super in static method with reserved name", ->
  class Foo
    @static: -> 'baz'

  class Bar extends Foo
    @static: -> super()

  eq Bar.static(), 'baz'

test "#3232: super in static methods (not object-assigned)", ->
  class Foo
    @baz = -> true
    @qux = -> true

  class Bar extends Foo
    @baz = -> super()
    Bar.qux = -> super()

  ok Bar.baz()
  ok Bar.qux()

test "#1392 calling `super` in methods defined on namespaced classes", ->
  class Base
    m: -> 5
    n: -> 4
  namespace =
    A: ->
    B: ->
  class namespace.A extends Base
    m: -> super()

  eq 5, (new namespace.A).m()
  namespace.B::m = namespace.A::m
  namespace.A::m = null
  eq 5, (new namespace.B).m()

  class C
    @a: class extends Base
      m: -> super()
  eq 5, (new C.a).m()


test "#4436 immediately instantiated named class", ->
  ok new class Foo


test "dynamic method names", ->
  class A
    "#{name = 'm'}": -> 1
  eq 1, new A().m()

  class B extends A
    "#{name = 'm'}": -> super()
  eq 1, new B().m()

  getName = -> 'm'
  class C
    "#{name = getName()}": -> 1
  eq 1, new C().m()


test "dynamic method names and super", ->
  class Base
    @m: -> 6
    m: -> 5
    m2: -> 4.5
    n: -> 4

  name = -> count++; 'n'
  count = 0

  m = 'm'
  class A extends Base
    "#{m}": -> super()
    "#{name()}": -> super()

  m = 'n'
  eq 5, (new A).m()

  eq 4, (new A).n()
  eq 1, count

  m = 'm'
  m2 = 'm2'
  count = 0
  class B extends Base
    @[name()] = -> super()
    "#{m}": -> super()
    "#{m2}": -> super()
  b = new B
  m = m2 = 'n'
  eq 6, B.m()
  eq 5, b.m()
  eq 4.5, b.m2()
  eq 1, count

  class C extends B
    m: -> super()
  eq 5, (new C).m()

# ES2015+ class interoperability
# Based on https://github.com/balupton/es6-javascript-class-interop
# Helper functions to generate true ES classes to extend:
getBasicClass = ->
  ```
  class BasicClass {
    constructor (greeting) {
      this.greeting = greeting || 'hi'
    }
  }
  ```
  BasicClass

getExtendedClass = (BaseClass) ->
  ```
  class ExtendedClass extends BaseClass {
    constructor (greeting, name) {
      super(greeting || 'hello')
      this.name = name
    }
  }
  ```
  ExtendedClass

test "can instantiate a basic ES class", ->
  BasicClass = getBasicClass()
  i = new BasicClass 'howdy!'
  eq i.greeting, 'howdy!'

test "can instantiate an extended ES class", ->
  BasicClass = getBasicClass()
  ExtendedClass = getExtendedClass BasicClass
  i = new ExtendedClass 'yo', 'buddy'
  eq i.greeting, 'yo'
  eq i.name, 'buddy'

test "can extend a basic ES class", ->
  BasicClass = getBasicClass()
  class ExtendedClass extends BasicClass
    constructor: (@name) ->
      super()
  i = new ExtendedClass 'dude'
  eq i.name, 'dude'

test "can extend an extended ES class", ->
  BasicClass = getBasicClass()
  ExtendedClass = getExtendedClass BasicClass

  class ExtendedExtendedClass extends ExtendedClass
    constructor: (@value) ->
      super()
    getDoubledValue: ->
      @value * 2

  i = new ExtendedExtendedClass 7
  eq i.getDoubledValue(), 14

test "CoffeeScript class can be extended in ES", ->
  class CoffeeClass
    constructor: (@favoriteDrink = 'latte', @size = 'grande') ->
    getDrinkOrder: ->
      "#{@size} #{@favoriteDrink}"

  ```
  class ECMAScriptClass extends CoffeeClass {
    constructor (favoriteDrink) {
      super(favoriteDrink);
      this.favoriteDrink = this.favoriteDrink + ' with a dash of semicolons';
    }
  }
  ```

  e = new ECMAScriptClass 'coffee'
  eq e.getDrinkOrder(), 'grande coffee with a dash of semicolons'

test "extended CoffeeScript class can be extended in ES", ->
  class CoffeeClass
    constructor: (@favoriteDrink = 'latte') ->

  class CoffeeClassWithDrinkOrder extends CoffeeClass
    constructor: (@favoriteDrink, @size = 'grande') ->
      super()
    getDrinkOrder: ->
      "#{@size} #{@favoriteDrink}"

  ```
  class ECMAScriptClass extends CoffeeClassWithDrinkOrder {
    constructor (favoriteDrink) {
      super(favoriteDrink);
      this.favoriteDrink = this.favoriteDrink + ' with a dash of semicolons';
    }
  }
  ```

  e = new ECMAScriptClass 'coffee'
  eq e.getDrinkOrder(), 'grande coffee with a dash of semicolons'

test "`this` access after `super` in extended classes", ->
  class Base

  class Test extends Base
    constructor: (param, @param) ->
      eq param, nonce

      result = { super: super(), @param, @method }
      eq result.super, this
      eq result.param, @param
      eq result.method, @method
      ok result.method isnt Test::method

    method: =>

  nonce = {}
  new Test nonce, {}

test "`@`-params and bound methods with multiple `super` paths (blocks)", ->
  nonce = {}

  class Base
    constructor: (@name) ->

  class Test extends Base
    constructor: (param, @param) ->
      if param
        super 'param'
        eq @name, 'param'
      else
        super 'not param'
        eq @name, 'not param'
      eq @param, nonce
      ok @method isnt Test::method
    method: =>
  new Test true, nonce
  new Test false, nonce


test "`@`-params and bound methods with multiple `super` paths (expressions)", ->
  nonce = {}

  class Base
    constructor: (@name) ->

  class Test extends Base
    constructor: (param, @param) ->
      # Contrived example: force each path into an expression with inline assertions
      if param
        result = (
          eq (super 'param'), @;
          eq @name, 'param';
          eq @param, nonce;
          ok @method isnt Test::method
        )
      else
        result = (
          eq (super 'not param'), @;
          eq @name, 'not param';
          eq @param, nonce;
          ok @method isnt Test::method
        )
    method: =>
  new Test true, nonce
  new Test false, nonce

test "constructor super in arrow functions", ->
  class Test extends (class)
    constructor: (@param) ->
      do => super()
      eq @param, nonce

  new Test nonce = {}

# TODO Some of these tests use CoffeeScript.compile and CoffeeScript.run when they could use
# regular test mechanics.
# TODO Some of these tests might be better placed in `test/error_messages.coffee`.
# TODO Some of these tests are duplicates.

# Ensure that we always throw if we experience more than one super()
# call in a constructor.  This ends up being a runtime error.
# Should be caught at compile time.
test "multiple super calls", ->
  throwsA = """
  class A
    constructor: (@drink) ->
    make: -> "Making a #{@drink}"

  class MultiSuper extends A
    constructor: (drink) ->
      super(drink)
      super(drink)
      @newDrink = drink
  new MultiSuper('Late').make()
  """
  throws -> CoffeeScript.run throwsA, bare: yes

# Basic test to ensure we can pass @params in a constuctor and
# inheritance works correctly
test "@ params", ->
  class A
    constructor: (@drink, @shots, @flavor) ->
    make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)"

  a = new A('Machiato', 2, 'chocolate')
  eq a.make(),  "Making a chocolate Machiato with 2 shot(s)"

  class B extends A
  b = new B('Machiato', 2, 'chocolate')
  eq b.make(),  "Making a chocolate Machiato with 2 shot(s)"

# Ensure we can accept @params with default parameters in a constructor
test "@ params with defaults in a constructor", ->
  class A
    # Multiple @ params with defaults
    constructor: (@drink = 'Americano', @shots = '1', @flavor = 'caramel') ->
    make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)"

  a = new A()
  eq a.make(),  "Making a caramel Americano with 1 shot(s)"

# Ensure we can handle default constructors with class params
test "@ params with class params", ->
  class Beverage
    drink: 'Americano'
    shots: '1'
    flavor: 'caramel'

  class A
    # Class creation as a default param with `this`
    constructor: (@drink = new Beverage()) ->
  a = new A()
  eq a.drink.drink, 'Americano'

  beverage = new Beverage
  class B
    # class costruction with a default external param
    constructor: (@drink = beverage) ->

  b = new B()
  eq b.drink.drink, 'Americano'

  class C
    # Default constructor with anonymous empty class
    constructor: (@meta = class) ->
  c = new C()
  ok c.meta instanceof Function

test "@ params without super, including errors", ->
  classA = """
  class A
    constructor: (@drink) ->
    make: -> "Making a #{@drink}"
  a = new A('Machiato')
  """

  throwsB = """
  class B extends A
    #implied super
    constructor: (@drink) ->
  b = new B('Machiato')
  """
  throwsCompileError classA + throwsB, bare: yes

test "@ params super race condition", ->
  classA = """
  class A
    constructor: (@drink) ->
    make: -> "Making a #{@drink}"
  """

  throwsB = """
  class B extends A
    constructor: (@params) ->

  b = new B('Machiato')
  """
  throwsCompileError classA + throwsB, bare: yes

  # Race condition with @ and super
  throwsC = """
  class C extends A
    constructor: (@params) ->
      super(@params)

  c = new C('Machiato')
  """
  throwsCompileError classA + throwsC, bare: yes


test "@ with super call", ->
  class D
    make: -> "Making a #{@drink}"

  class E extends D
    constructor: (@drink) ->
      super()

  e = new E('Machiato')
  eq e.make(),  "Making a Machiato"

test "@ with splats and super call", ->
  class A
    make: -> "Making a #{@drink}"

  class B extends A
    constructor: (@drink...) ->
      super()

  B = new B('Machiato')
  eq B.make(),  "Making a Machiato"


test "super and external constructors", ->
  # external constructor with @ param is allowed
  ctorA = (@drink) ->
  class A
    constructor: ctorA
    make: -> "Making a #{@drink}"
  a = new A('Machiato')
  eq a.make(),  "Making a Machiato"

  # External constructor with super
  throwsC = """
  class B
    constructor: (@drink) ->
    make: -> "Making a #{@drink}"

  ctorC = (drink) ->
    super(drink)

  class C extends B
    constructor: ctorC
  c = new C('Machiato')
  """
  throwsCompileError throwsC, bare: yes


test "bound functions without super", ->
  # Bound function with @
  # Throw on compile, since bound
  # constructors are illegal
  throwsA = """
  class A
    constructor: (drink) =>
      @drink = drink

  """
  throwsCompileError throwsA, bare: yes

test "super in a bound function in a constructor", ->
  throwsB = """
  class A
  class B extends A
    constructor: do => super
  """
  throwsCompileError throwsB, bare: yes

test "super in a bound function", ->
  class A
    constructor: (@drink) ->
    make: -> "Making a #{@drink}"

  class B extends A
    make: (@flavor) =>
      super() + " with #{@flavor}"

  b = new B('Machiato')
  eq b.make('vanilla'),  "Making a Machiato with vanilla"

  # super in a bound function in a bound function
  class C extends A
    make: (@flavor) =>
      func = () =>
        super() + " with #{@flavor}"
      func()

  c = new C('Machiato')
  eq c.make('vanilla'), "Making a Machiato with vanilla"

  # bound function in a constructor
  class D extends A
    constructor: (drink) ->
      super(drink)
      x = =>
        eq @drink,  "Machiato"
      x()
  d = new D('Machiato')
  eq d.make(),  "Making a Machiato"

# duplicate
test "super in a try/catch", ->
  classA = """
  class A
    constructor: (param) ->
      throw "" unless param
  """

  throwsB = """
  class B extends A
      constructor: ->
        try
          super()
  """

  throwsC = """
  ctor = ->
    try
      super()

  class C extends A
      constructor: ctor
  """
  throws -> CoffeeScript.run classA + throwsB, bare: yes
  throws -> CoffeeScript.run classA + throwsC, bare: yes

test "mixed ES6 and CS6 classes with a four-level inheritance chain", ->
  # Extended test
  # ES2015+ class interoperability

  ```
  class Base {
    constructor (greeting) {
      this.greeting = greeting || 'hi';
    }
    func (string) {
      return 'zero/' + string;
    }
    static  staticFunc (string) {
      return 'static/' + string;
    }
  }
  ```

  class FirstChild extends Base
    func: (string) ->
      super('one/') + string


  ```
  class SecondChild extends FirstChild {
    func (string) {
      return super.func('two/' + string);
    }
  }
  ```

  thirdCtor = ->
    @array = [1, 2, 3]

  class ThirdChild extends SecondChild
    constructor: ->
      super()
      thirdCtor.call this
    func: (string) ->
      super('three/') + string

  result = (new ThirdChild).func 'four'
  ok result is 'zero/one/two/three/four'
  ok Base.staticFunc('word') is 'static/word'

# exercise extends in a nested class
test "nested classes with super", ->
  class Outer
    constructor: ->
      @label = 'outer'

    class @Inner
      constructor: ->
        @label = 'inner'

    class @ExtendedInner extends @Inner
      constructor: ->
        tmp = super()
        @label = tmp.label + ' extended'

    @extender: () =>
      class ExtendedSelf extends @
        constructor: ->
          tmp = super()
          @label = tmp.label + ' from this'
      new ExtendedSelf

  eq (new Outer).label, 'outer'
  eq (new Outer.Inner).label, 'inner'
  eq (new Outer.ExtendedInner).label, 'inner extended'
  eq (Outer.extender()).label, 'outer from this'

test "Static methods generate 'static' keywords", ->
  compile = """
  class CheckStatic
    constructor: (@drink) ->
    @className: -> 'CheckStatic'

  c = new CheckStatic('Machiato')
  """
  result = CoffeeScript.compile compile, bare: yes
  ok result.match(' static ')

test "Static methods in nested classes", ->
  class Outer
    @name: -> 'Outer'

    class @Inner
      @name: -> 'Inner'

  eq Outer.name(), 'Outer'
  eq Outer.Inner.name(), 'Inner'


test "mixed constructors with inheritance and ES6 super", ->
  identity = (f) -> f

  class TopClass
    constructor: (arg) ->
      @prop = 'top-' + arg

  ```
  class SuperClass extends TopClass {
    constructor (arg) {
      identity(super('super-' + arg));
    }
  }
  ```
  class SubClass extends SuperClass
    constructor: ->
      identity super 'sub'

  ok (new SubClass).prop is 'top-super-sub'

test "ES6 static class methods can be overriden", ->
  class A
    @name: -> 'A'

  class B extends A
    @name: -> 'B'

  eq A.name(), 'A'
  eq B.name(), 'B'

# If creating static by direct assignment rather than ES6 static keyword
test "ES6 Static methods should set `this` to undefined // ES6 ", ->
  class A
    @test: ->
      eq this, undefined

# Ensure that our object prototypes work with ES6
test "ES6 prototypes can be overriden", ->
  class A
    className: 'classA'

  ```
  class B {
    test () {return "B";};
  }
  ```
  b = new B
  a = new A
  eq a.className, 'classA'
  eq b.test(), 'B'
  Object.setPrototypeOf(b, a)
  eq b.className, 'classA'
  # This shouldn't throw,
  # as we only change inheritance not object construction
  # This may be an issue with ES, rather than CS construction?
  #eq b.test(), 'B'

  class D extends B
  B::test = () -> 'D'
  eq (new D).test(), 'D'

# TODO: implement this error check
# test "ES6 conformance to extending non-classes", ->
#   A = (@title) ->
#     'Title: ' + @

#   class B extends A
#   b = new B('caffeinated')
#   eq b.title, 'caffeinated'

#   # Check inheritance chain
#   A::getTitle = () -> @title
#   eq b.getTitle(), 'caffeinated'

#   throwsC = """
#   C = {title: 'invalid'}
#   class D extends {}
#   """
#   # This should catch on compile and message should be "class can only extend classes and functions."
#   throws -> CoffeeScript.run throwsC, bare: yes

# TODO: Evaluate future compliance with "strict mode";
# test "Class function environment should be in `strict mode`, ie as if 'use strict' was in use", ->
#   class A
#     # this might be a meaningless test, since these are likely to be runtime errors and different
#     # for every browser.  Thoughts?
#     constructor: () ->
#       # Ivalid: prop reassignment
#       @state = {prop: [1], prop: {a: 'a'}}
#       # eval reassignment
#       @badEval = eval;

#   # Should throw, but doesn't
#   a = new A

test "only one method named constructor allowed", ->
  throwsA = """
  class A
    constructor: (@first) ->
    constructor: (@last) ->
  """
  throwsCompileError throwsA, bare: yes

test "If the constructor of a child class does not call super,it should return an object.", ->
  nonce = {}

  class A
  class B extends A
    constructor: ->
      return nonce

  eq nonce, new B


test "super can only exist in extended classes", ->
  throwsA = """
  class A
    constructor: (@name) ->
      super()
  """
  throwsCompileError throwsA, bare: yes

# --- CS1 classes compatability breaks ---
test "CS6 Class extends a CS1 compiled class", ->
  ```
  // Generated by CoffeeScript 1.11.1
  var BaseCS1, ExtendedCS1,
    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  BaseCS1 = (function() {
    function BaseCS1(drink) {
      this.drink = drink;
    }

    BaseCS1.prototype.make = function() {
      return "making a " + this.drink;
    };

    BaseCS1.className = function() {
      return 'BaseCS1';
    };

    return BaseCS1;

  })();

  ExtendedCS1 = (function(superClass) {
    extend(ExtendedCS1, superClass);

    function ExtendedCS1(flavor) {
      this.flavor = flavor;
      ExtendedCS1.__super__.constructor.call(this, 'cafe ole');
    }

    ExtendedCS1.prototype.make = function() {
      return "making a " + this.drink + " with " + this.flavor;
    };

    ExtendedCS1.className = function() {
      return 'ExtendedCS1';
    };

    return ExtendedCS1;

  })(BaseCS1);

  ```
  class B extends BaseCS1
  eq B.className(), 'BaseCS1'
  b = new B('machiato')
  eq b.make(), "making a machiato"


test "CS6 Class extends an extended CS1 compiled class", ->
  ```
  // Generated by CoffeeScript 1.11.1
  var BaseCS1, ExtendedCS1,
    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  BaseCS1 = (function() {
    function BaseCS1(drink) {
      this.drink = drink;
    }

    BaseCS1.prototype.make = function() {
      return "making a " + this.drink;
    };

    BaseCS1.className = function() {
      return 'BaseCS1';
    };

    return BaseCS1;

  })();

  ExtendedCS1 = (function(superClass) {
    extend(ExtendedCS1, superClass);

    function ExtendedCS1(flavor) {
      this.flavor = flavor;
      ExtendedCS1.__super__.constructor.call(this, 'cafe ole');
    }

    ExtendedCS1.prototype.make = function() {
      return "making a " + this.drink + " with " + this.flavor;
    };

    ExtendedCS1.className = function() {
      return 'ExtendedCS1';
    };

    return ExtendedCS1;

  })(BaseCS1);

  ```
  class B extends ExtendedCS1
  eq B.className(), 'ExtendedCS1'
  b = new B('vanilla')
  eq b.make(), "making a cafe ole with vanilla"

test "CS6 Class extends a CS1 compiled class with super()", ->
  ```
  // Generated by CoffeeScript 1.11.1
  var BaseCS1, ExtendedCS1,
    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  BaseCS1 = (function() {
    function BaseCS1(drink) {
      this.drink = drink;
    }

    BaseCS1.prototype.make = function() {
      return "making a " + this.drink;
    };

    BaseCS1.className = function() {
      return 'BaseCS1';
    };

    return BaseCS1;

  })();

  ExtendedCS1 = (function(superClass) {
    extend(ExtendedCS1, superClass);

    function ExtendedCS1(flavor) {
      this.flavor = flavor;
      ExtendedCS1.__super__.constructor.call(this, 'cafe ole');
    }

    ExtendedCS1.prototype.make = function() {
      return "making a " + this.drink + " with " + this.flavor;
    };

    ExtendedCS1.className = function() {
      return 'ExtendedCS1';
    };

    return ExtendedCS1;

  })(BaseCS1);

  ```
  class B extends ExtendedCS1
    constructor: (@shots) ->
      super('caramel')
    make: () ->
      super() + " and #{@shots} shots of espresso"

  eq B.className(), 'ExtendedCS1'
  b = new B('three')
  eq b.make(), "making a cafe ole with caramel and three shots of espresso"

test 'Bound method called normally before binding is ok', ->
  class Base
    constructor: ->
      @setProp()
      eq @derivedBound(), 3

  class Derived extends Base
    setProp: ->
      @prop = 3

    derivedBound: =>
      @prop

  d = new Derived

test 'Bound method called as callback after super() is ok', ->
  class Base

  class Derived extends Base
    constructor: (@prop = 3) ->
      super()
      f = @derivedBound
      eq f(), 3

    derivedBound: =>
      @prop

  d = new Derived
  {derivedBound} = d
  eq derivedBound(), 3

test 'Bound method of base class called as callback is ok', ->
  class Base
    constructor: (@prop = 3) ->
      f = @baseBound
      eq f(), 3

    baseBound: =>
      @prop

  b = new Base
  {baseBound} = b
  eq baseBound(), 3

test 'Bound method of prop-named class called as callback is ok', ->
  Hive = {}
  class Hive.Bee
    constructor: (@prop = 3) ->
      f = @baseBound
      eq f(), 3

    baseBound: =>
      @prop

  b = new Hive.Bee
  {baseBound} = b
  eq baseBound(), 3

test 'Bound method of class with expression base class called as callback is ok', ->
  calledB = no
  B = ->
    throw new Error if calledB
    calledB = yes
    class
  class A extends B()
    constructor: (@prop = 3) ->
      super()
      f = @derivedBound
      eq f(), 3

    derivedBound: =>
      @prop

  b = new A
  {derivedBound} = b
  eq derivedBound(), 3

test 'Bound method of class with expression class name called as callback is ok', ->
  calledF = no
  obj = {}
  B = class
  f = ->
    throw new Error if calledF
    calledF = yes
    obj
  class f().A extends B
    constructor: (@prop = 3) ->
      super()
      g = @derivedBound
      eq g(), 3

    derivedBound: =>
      @prop

  a = new obj.A
  {derivedBound} = a
  eq derivedBound(), 3

test 'Bound method of anonymous child class called as callback is ok', ->
  f = ->
    B = class
    class extends B
      constructor: (@prop = 3) ->
        super()
        g = @derivedBound
        eq g(), 3

      derivedBound: =>
        @prop

  a = new (f())
  {derivedBound} = a
  eq derivedBound(), 3

test 'Bound method of immediately instantiated class with expression base class called as callback is ok', ->
  calledF = no
  obj = {}
  B = class
  f = ->
    throw new Error if calledF
    calledF = yes
    obj
  a = new class f().A extends B
    constructor: (@prop = 3) ->
      super()
      g = @derivedBound
      eq g(), 3

    derivedBound: =>
      @prop

  {derivedBound} = a
  eq derivedBound(), 3

test "#4591: super.x.y, super['x'].y", ->
  class A
    x:
      y: 1
      z: -> 2

  class B extends A
    constructor: ->
      super()

      @w = super.x.y
      @v = super['x'].y
      @u = super.x['y']
      @t = super.x.z()
      @s = super['x'].z()
      @r = super.x['z']()

  b = new B
  eq 1, b.w
  eq 1, b.v
  eq 1, b.u
  eq 2, b.t
  eq 2, b.s
  eq 2, b.r

test "#4464: backticked expressions in class body", ->
  class A
    `get x() { return 42; }`

  class B
    `get x() { return 42; }`
    constructor: ->
      @y = 84

  a = new A
  eq 42, a.x
  b = new B
  eq 42, b.x
  eq 84, b.y

test "#4724: backticked expression in a class body with hoisted member", ->
  class A
    `get x() { return 42; }`
    hoisted: 84

  a = new A
  eq 42, a.x
  eq 84, a.hoisted

test "#4822: nested anonymous classes use non-conflicting variable names", ->
  Class = class
    @a: class
      @b: 1

  eq Class.a.b, 1

test "#4827: executable class body wrappers have correct context", ->
  test = ->
    class @A
    class @B extends @A
      @property = 1

  o = {}
  test.call o
  ok typeof o.A is typeof o.B is 'function'

test "#4868: Incorrect ‘Can’t call super with @params’ error", ->
  class A
    constructor: (@func = ->) ->
      @x = 1
      @func()

  class B extends A
    constructor: ->
      super -> @x = 2

  a = new A
  b = new B
  eq 1, a.x
  eq 2, b.x

  class C
    constructor: (@c = class) -> @c

  class D extends C
    constructor: ->
      super class then constructor: (@a) -> @a = 3

  d = new (new D).c
  eq 3, d.a

test "#4609: Support new.target", ->
  class A
    constructor: ->
      @calledAs = new.target.name

  class B extends A

  b = new B
  eq b.calledAs, 'B'

  newTarget = null
  Foo = ->
    newTarget = !!new.target

  Foo()
  eq newTarget, no

  newTarget = null

  new Foo()
  eq newTarget, yes

test "#5323: new.target can be the argument of a function", ->
  fn = (arg) -> arg
  fn new.target

test "#5085: Bug: @ reference to class not maintained in do block", ->
  thisFoo = 'initial foo'
  thisBar = 'initial bar'
  fn = (o) -> o.bar()

  class A
    @foo = 'foo assigned in class'
    do => thisFoo = @foo
    fn bar: => thisBar = @foo

  eq thisFoo, 'foo assigned in class'
  eq thisBar, 'foo assigned in class'

test "#5204: Computed class property", ->
  foo = 'bar'
  class A
    [foo]: 'baz'
  a = new A()
  eq a.bar, 'baz'
  eq A::bar, 'baz'

test "#5204: Static computed class property", ->
  foo = 'bar'
  qux = 'quux'
  class A
    @[foo]: 'baz'
    @[qux]: -> 3
  eq A.bar, 'baz'
  eq A.quux(), 3

</script>
<script type="text/x-coffeescript" class="test" id="cluster">
# Cluster Module
# ---------

return if testingBrowser?

cluster = require 'cluster'

if cluster.isMaster
  test "#2737 - cluster module can spawn workers from a coffeescript process", ->
    cluster.once 'exit', (worker, code) ->
      eq code, 0

    cluster.fork()
else
  process.exit 0

</script>
<script type="text/x-coffeescript" class="test" id="comments">
# Comments
# --------

# * Single-Line Comments
# * Block Comments

# Note: awkward spacing seen in some tests is likely intentional.

test "comments in objects", ->
  obj1 = {
  # comment
    # comment
      # comment
    one: 1
  # comment
    two: 2
      # comment
  }

  ok Object::hasOwnProperty.call(obj1,'one')
  eq obj1.one, 1
  ok Object::hasOwnProperty.call(obj1,'two')
  eq obj1.two, 2

test "comments in YAML-style objects", ->
  obj2 =
  # comment
    # comment
      # comment
    three: 3
  # comment
    four: 4
      # comment

  ok Object::hasOwnProperty.call(obj2,'three')
  eq obj2.three, 3
  ok Object::hasOwnProperty.call(obj2,'four')
  eq obj2.four, 4

test "comments following operators that continue lines", ->
  sum =
    1 +
    1 + # comment
    1
  eq 3, sum

test "comments in functions", ->
  fn = ->
  # comment
    false
    false   # comment
    false
    # comment

  # comment before return
    true

  ok fn()

  fn2 = -> #comment
    fn()
    # comment after return

  ok fn2()

test "trailing comment before an outdent", ->
  nonce = {}
  fn3 = ->
    if true
      undefined # comment
    nonce

  eq nonce, fn3()

test "comments in a switch", ->
  nonce = {}
  result = switch nonce #comment
    # comment
    when false then undefined
    # comment
    when null #comment
      undefined
    else nonce # comment

  eq nonce, result

test "comment with conditional statements", ->
  nonce = {}
  result = if false # comment
    undefined
  #comment
  else # comment
    nonce
    # comment
  eq nonce, result

test "spaced comments with conditional statements", ->
  nonce = {}
  result = if false
    undefined

  # comment
  else if false
    undefined

  # comment
  else
    nonce

  eq nonce, result


# Block Comments

###
  This is a here-comment.
  Kind of like a heredoc.
###

test "block comments in objects", ->
  a = {}
  b = {}
  obj = {
    a: a
    ###
    block comment in object
    ###
    b: b
  }

  eq a, obj.a
  eq b, obj.b

test "block comments in YAML-style", ->
  a = {}
  b = {}
  obj =
    a: a
    ###
    block comment in YAML-style
    ###
    b: b

  eq a, obj.a
  eq b, obj.b


test "block comments in functions", ->
  nonce = {}

  fn1 = ->
    true
    ###
    false
    ###

  ok fn1()

  fn2 = ->
    ###
    block comment in function 1
    ###
    nonce

  eq nonce, fn2()

  fn3 = ->
    nonce
  ###
  block comment in function 2
  ###

  eq nonce, fn3()

  fn4 = ->
    one = ->
      ###
        block comment in function 3
      ###
      two = ->
        three = ->
          nonce

  eq nonce, fn4()()()()

test "block comments inside class bodies", ->
  class A
    a: ->

    ###
    Comment in class body 1
    ###
    b: ->

  ok A.prototype.b instanceof Function

  class B
    ###
    Comment in class body 2
    ###
    a: ->
    b: ->

  ok B.prototype.a instanceof Function

test "#2037: herecomments shouldn't imply line terminators", ->
  do (-> ### ###yes; fail)

test "#2916: block comment before implicit call with implicit object", ->
  fn = (obj) -> ok obj.a
  ### ###
  fn
    a: yes

test "#3132: Format single-line block comment nicely", ->
  eqJS """
  ### Single-line block comment without additional space here => ###""",
  """
  /* Single-line block comment without additional space here => */
  """

test "#3132: Format multiline block comment nicely", ->
  eqJS """
  ###
  # Multiline
  # block
  # comment
  ###""",
  """
  /*
   * Multiline
   * block
   * comment
   */
  """

test "#3132: Format simple block comment nicely", ->
  eqJS """
  ###
  No
  Preceding hash
  ###""",
  """
  /*
  No
  Preceding hash
  */
  """


test "#3132: Format indented block-comment nicely", ->
  eqJS """
  fn = ->
    ###
    # Indented
    Multiline
    ###
    1""",
  """
  var fn;

  fn = function() {
    /*
     * Indented
    Multiline
     */
    return 1;
  };
  """

# Although adequately working, block comment-placement is not yet perfect.
# (Considering a case where multiple variables have been declared …)
test "#3132: Format jsdoc-style block-comment nicely", ->
  eqJS """
  ###*
  # Multiline for jsdoc-"@doctags"
  #
  # @type {Function}
  ###
  fn = () -> 1
  """,
  """
  /**
   * Multiline for jsdoc-"@doctags"
   *
   * @type {Function}
   */
  var fn;

  fn = function() {
    return 1;
  };"""

# Although adequately working, block comment-placement is not yet perfect.
# (Considering a case where multiple variables have been declared …)
test "#3132: Format hand-made (raw) jsdoc-style block-comment nicely", ->
  eqJS """
  ###*
   * Multiline for jsdoc-"@doctags"
   *
   * @type {Function}
  ###
  fn = () -> 1
  """,
  """
  /**
   * Multiline for jsdoc-"@doctags"
   *
   * @type {Function}
   */
  var fn;

  fn = function() {
    return 1;
  };"""

# Although adequately working, block comment-placement is not yet perfect.
# (Considering a case where multiple variables have been declared …)
test "#3132: Place block-comments nicely", ->
  eqJS """
  ###*
  # A dummy class definition
  #
  # @class
  ###
  class DummyClass

    ###*
    # @constructor
    ###
    constructor: ->

    ###*
    # Singleton reference
    #
    # @type {DummyClass}
    ###
    @instance = new DummyClass()

  """,
  """
  /**
   * A dummy class definition
   *
   * @class
   */
  var DummyClass;

  DummyClass = (function() {
    class DummyClass {
      /**
       * @constructor
       */
      constructor() {}

    };

    /**
     * Singleton reference
     *
     * @type {DummyClass}
     */
    DummyClass.instance = new DummyClass();

    return DummyClass;

  }).call(this);"""

test "#3638: Demand a whitespace after # symbol", ->
  eqJS """
  ###
  #No
  #whitespace
  ###""",
  """
  /*
  #No
  #whitespace
   */"""


test "#3761: Multiline comment at end of an object", ->
  anObject =
    x: 3
    ###
    #Comment
    ###

  ok anObject.x is 3

test "#4375: UTF-8 characters in comments", ->
  # 智に働けば角が立つ、情に掉させば流される。
  ok yes

test "#4290: Block comments in array literals", ->
  arr = [
    ###  ###
    3
    ###
      What is the meaning of life, the universe, and everything?
    ###
    42
  ]
  arrayEq arr, [3, 42]

test "Block comments in array literals are properly indented 1", ->
  eqJS '''
  arr = [
    ### ! ###
    3
    42
  ]''', '''
  var arr;

  arr = [/* ! */ 3, 42];'''

test "Block comments in array literals are properly indented 2", ->
  eqJS '''
  arr = [
    ###  ###
    3
    ###
      What is the meaning of life, the universe, and everything?
    ###
    42
  ]''', '''
  var arr;

  arr = [
    /*  */
    3,
    /*
      What is the meaning of life, the universe, and everything?
    */
    42
  ];'''

test "Block comments in array literals are properly indented 3", ->
  eqJS '''
  arr = [
    ###
      How many stooges are there?
    ###
    3
    ### Who’s on first? ###
    'Who'
  ]''', '''
  var arr;

  arr = [
    /*
      How many stooges are there?
    */
    3,
    /* Who’s on first? */
    'Who'
  ];'''

test "Block comments in array literals are properly indented 4", ->
  eqJS '''
  if yes
    arr = [
      1
      ###
        How many stooges are there?
      ###
      3
      ### Who’s on first? ###
      'Who'
    ]''', '''
  var arr;

  if (true) {
    arr = [
      1,
      /*
        How many stooges are there?
      */
      3,
      /* Who’s on first? */
      'Who'
    ];
  }'''

test "Line comments in array literals are properly indented 1", ->
  eqJS '''
  arr = [
    # How many stooges are there?
    3
    # Who’s on first?
    'Who'
  ]''', '''
  var arr;

  arr = [
    // How many stooges are there?
    3,
    // Who’s on first?
    'Who'
  ];'''

test "Line comments in array literals are properly indented 2", ->
  eqJS '''
  arr = [
    # How many stooges are there?
    3
    # Who’s on first?
    'Who'
    # Who?
    {
      firstBase: 'Who'
      secondBase: 'What'
      leftField: 'Why'
    }
  ]''', '''
  var arr;

  arr = [
    // How many stooges are there?
    3,
    // Who’s on first?
    'Who',
    {
      // Who?
      firstBase: 'Who',
      secondBase: 'What',
      leftField: 'Why'
    }
  ];'''

test "Block comments trailing their attached token are properly indented", ->
  eqJS '''
  if indented
    if indentedAgain
      a
      ###
        Multiline
        comment
      ###
    a
  ''', '''
  if (indented) {
    if (indentedAgain) {
      a;
    }
    /*
      Multiline
      comment
    */
    a;
  }
  '''

test "Comments in proper order 1", ->
  eqJS '''
  # 1
  ### 2 ###
  # 3
  ''', '''
  // 1
  /* 2 */
  // 3
  '''

test "Comments in proper order 2", ->
  eqJS '''
  if indented
    # 1
    ### 2 ###
    # 3
    a
  ''', '''
  if (indented) {
    // 1
    /* 2 */
    // 3
    a;
  }
  '''

test "Line comment above interpolated string", ->
  eqJS '''
  if indented
    # comment
    "#{1}"
  ''', '''
  if (indented) {
    // comment
    `${1}`;
  }'''

test "Line comment above interpolated string object key", ->
  eqJS '''
  {
    # comment
    "#{1}": 2
  }
  ''', '''
  ({
    // comment
    [`${1}`]: 2
  });'''

test "Line comments in classes are properly indented", ->
  eqJS '''
  class A extends B
    # This is a fine class.
    # I could tell you all about it, but what else do you need to know?
    constructor: ->
      # Something before `super`
      super()

    # This next method is a doozy!
    # A doozy, I tell ya!
    method: ->
      # Whoa.
      # Can you believe it?
      no

    ### Look out, incoming! ###
    anotherMethod: ->
      ### Ha! ###
      off
  ''', '''
  var A;

  A = class A extends B {
    // This is a fine class.
    // I could tell you all about it, but what else do you need to know?
    constructor() {
      // Something before `super`
      super();
    }

    // This next method is a doozy!
    // A doozy, I tell ya!
    method() {
      // Whoa.
      // Can you believe it?
      return false;
    }

    /* Look out, incoming! */
    anotherMethod() {
      /* Ha! */
      return false;
    }

  };'''

test "Line comments are properly indented", ->
  eqJS '''
  # Unindented comment
  if yes
    # Comment indented one tab
    1
    if yes
      # Comment indented two tabs
      2
    else
      # Another comment indented two tabs
      # Yet another comment indented two tabs
      3
  else
    # Another comment indented one tab
    # Yet another comment indented one tab
    4

  # Another unindented comment''', '''
  // Unindented comment
  if (true) {
    // Comment indented one tab
    1;
    if (true) {
      // Comment indented two tabs
      2;
    } else {
      // Another comment indented two tabs
      // Yet another comment indented two tabs
      3;
    }
  } else {
    // Another comment indented one tab
    // Yet another comment indented one tab
    4;
  }

  // Another unindented comment'''

test "Line comments that trail code, followed by line comments that start a new line", ->
  eqJS '''
  a = ->
    b 1 # Trailing comment

  # Comment that starts a new line
  2
  ''', '''
  var a;

  a = function() {
    return b(1); // Trailing comment
  };


  // Comment that starts a new line
  2;
  '''

test "Empty lines between comments are preserved", ->
  eqJS '''
  if indented
    # 1

    # 2
    3
  ''', '''
  if (indented) {
    // 1

    // 2
    3;
  }'''

test "Block comment in an interpolated string", ->
  eqJS '"a#{### Comment ###}b"', '`a${/* Comment */""}b`;'
  eqJS '"a#{### 1 ###}b#{### 2 ###}c"', '`a${/* 1 */""}b${/* 2 */""}c`;'

test "#4629: Block comment in JSX interpolation", ->
  eqJS '<div>{### Comment ###}</div>', '<div>{/* Comment */}</div>;'
  eqJS '''
  <div>
  {###
    Multiline
    Comment
  ###}
  </div>''', '''
  <div>
  {/*
    Multiline
    Comment
  */}
  </div>;'''

test "Line comment in an interpolated string", ->
  eqJS '''
  "a#{# Comment
  1}b"
  ''', '''
  `a${// Comment
  1}b`;'''

test "Line comments before `throw`", ->
  eqJS '''
  if indented
    1/0
    # Uh-oh!
    # You really shouldn’t have done that.
    throw DivideByZeroError()
  ''', '''
  if (indented) {
    1 / 0;
    // Uh-oh!
    // You really shouldn’t have done that.
    throw DivideByZeroError();
  }'''

test "Comments before if this exists", ->
  js = CoffeeScript.compile '''
  1
  # Comment
  if @huh?
    2
  '''
  ok js.includes '// Comment'

test "Comment before unary (`not`)", ->
  js = CoffeeScript.compile '''
  1
  # Comment
  if not doubleNegative
    dontDoIt()
  '''
  ok js.includes '// Comment'

test "Comments before postfix", ->
  js = CoffeeScript.compile '''
  # 1
  2

  # 3
  return unless window?

  ### 4 ###
  return if global?
  '''
  ok js.includes '// 3'
  ok js.includes '/* 4 */'

test "Comments before assignment if", ->
  js = CoffeeScript.compile '''
  1
  # Line comment
  a = if b
    3
  else
    4

  ### Block comment ###
  c = if d
    5
  '''
  ok js.includes '// Line comment'
  ok js.includes '/* Block comment */'

test "Comments before for loop", ->
  js = CoffeeScript.compile '''
  1
  # Comment
  for drop in ocean
    drink drop
  '''
  ok js.includes '// Comment'

test "Comments after for loop", ->
  js = CoffeeScript.compile '''
  for drop in ocean # Comment after source variable
    drink drop
  for i in [1, 2] # Comment after array literal element
    count i
  for key, val of {a: 1} # Comment after object literal
    turn key
  '''
  ok js.includes '// Comment after source variable'
  ok js.includes '// Comment after array literal element'
  ok js.includes '// Comment after object literal'

test "Comments before soak", ->
  js = CoffeeScript.compile '''
  # 1
  2

  # 3
  return unless window?.location?.hash

  ### 4 ###
  return if process?.env?.ENV
  '''
  ok js.includes '// 3'
  ok js.includes '/* 4 */'

test "Comments before splice", ->
  js = CoffeeScript.compile '''
  1
  # Comment
  a[1..2] = [1, 2, 3]
  '''
  ok js.includes '// Comment'

test "Comments before object destructuring", ->
  js = CoffeeScript.compile '''
  1
  # Comment before splat token
  { x... } = { a: 1, b: 2 }

  # Comment before destructured token
  { x, y, z... } = { x: 1, y: 2, a: 3, b: 4 }
  '''
  ok js.includes 'Comment before splat token'
  ok js.includes 'Comment before destructured token'

test "Comment before splat function parameter", ->
  js = CoffeeScript.compile '''
  1
  # Comment
  (blah..., yadda) ->
  '''
  ok js.includes 'Comment'

test "Comments before static method", ->
  eqJS '''
  class Child extends Base
    # Static method:
    @method = ->
  ''', '''
  var Child;

  Child = class Child extends Base {
    // Static method:
    static method() {}

  };'''

test "Comment before method that calls `super()`", ->
  eqJS '''
  class Dismissed
    # Before a method calling `super`
    method: ->
      super()
  ''', '''
  var Dismissed;

  Dismissed = class Dismissed {
    // Before a method calling `super`
    method() {
      return super.method();
    }

  };
  '''

test "Comment in interpolated regex", ->
  js = CoffeeScript.compile '''
  1
  ///
    #{1}
    # Comment
  ///
  '''
  ok js.includes 'Comment'

test "Line comment after line continuation", ->
  eqJS '''
  1 + \\ # comment
    2
  ''', '''
  1 + 2; // comment
  '''

test "Comments appear above scope `var` declarations", ->
  eqJS '''
  # @flow

  fn = (str) -> str
  ''', '''
  // @flow
  var fn;

  fn = function(str) {
    return str;
  };'''

test "Block comments can appear with function arguments", ->
  eqJS '''
  fn = (str ###: string ###, num ###: number ###) -> str + num
  ''', '''
  var fn;

  fn = function(str/*: string */, num/*: number */) {
    return str + num;
  };'''

test "Block comments can appear between function parameters and function opening brace", ->
  eqJS '''
  fn = (str ###: string ###, num ###: number ###) ###: string ### ->
    str + num
  ''', '''
  var fn;

  fn = function(str/*: string */, num/*: number */)/*: string */ {
    return str + num;
  };'''

test "Flow comment-based syntax support", ->
  eqJS '''
  # @flow

  fn = (str ###: string ###, num ###: number ###) ###: string ### ->
    str + num
  ''', '''
  // @flow
  var fn;

  fn = function(str/*: string */, num/*: number */)/*: string */ {
    return str + num;
  };'''

test "#4706: Flow comments around function parameters", ->
  eqJS '''
  identity = ###::<T>### (value ###: T ###) ###: T ### ->
    value
  ''', '''
  var identity;

  identity = function/*::<T>*/(value/*: T */)/*: T */ {
    return value;
  };'''

test "#4706: Flow comments around function parameters", ->
  eqJS '''
  copy = arr.map(###:: <T> ###(item ###: T ###) ###: T ### => item)
  ''', '''
  var copy;

  copy = arr.map(/*:: <T> */(item/*: T */)/*: T */ => {
    return item;
  });'''

test "#4706: Flow comments after class name", ->
  eqJS '''
  class Container ###::<T> ###
    method: ###::<U> ### () -> true
  ''', '''
  var Container;

  Container = class Container/*::<T> */ {
    method/*::<U> */() {
      return true;
    }

  };'''

test "#4706: Identifiers with comments wrapped in parentheses remain wrapped", ->
  eqJS '(arr ###: Array<number> ###)', '(arr/*: Array<number> */);'
  eqJS 'other = (arr ###: any ###)', '''
  var other;

  other = (arr/*: any */);'''

test "#4706: Flow comments before class methods", ->
  eqJS '''
  class Container
    ###::
    method: (number) => string;
    method: (string) => number;
    ###
    method: -> true
  ''', '''
  var Container;

  Container = class Container {
    /*::
    method: (number) => string;
    method: (string) => number;
    */
    method() {
      return true;
    }

  };'''

test "#4706: Flow comments for class method params", ->
  eqJS '''
  class Container
    method: (param ###: string ###) -> true
  ''', '''
  var Container;

  Container = class Container {
    method(param/*: string */) {
      return true;
    }

  };'''

test "#4706: Flow comments for class method returns", ->
  eqJS '''
  class Container
    method: () ###: string ### -> true
  ''', '''
  var Container;

  Container = class Container {
    method()/*: string */ {
      return true;
    }

  };'''

test "#4706: Flow comments for function spread", ->
  eqJS '''
  method = (...rest ###: Array<string> ###) =>
  ''', '''
  var method;

  method = (...rest/*: Array<string> */) => {};'''

test "#4747: Flow comments for local variable declaration", ->
  eqJS 'a ###: number ### = 1', '''
  var a/*: number */;

  a = 1;
  '''

test "#4747: Flow comments for local variable declarations", ->
  eqJS '''
  a ###: number ### = 1
  b ###: string ### = 'c'
  ''', '''
  var a/*: number */, b/*: string */;

  a = 1;

  b = 'c';
  '''

test "#4747: Flow comments for local variable declarations with reassignment", ->
  eqJS '''
  a ###: number ### = 1
  b ###: string ### = 'c'
  a ### some other comment ### = 2
  ''', '''
  var a/*: number */, b/*: string */;

  a = 1;

  b = 'c';

  a/* some other comment */ = 2;
  '''

test "#4756: Comment before ? operation", ->
  eqJS '''
  do ->
    ### Comment ###
    @foo ? 42
  ''', '''
  (function() {
    var ref;
    /* Comment */
    return (ref = this.foo) != null ? ref : 42;
  })();
  '''

test "#5241: Comment after :", ->
  eqJS '''
  return
    a: # Comment
      b: 1
  ''', '''
  return {
    a: { // Comment
      b: 1
    }
  };
  '''

</script>
<script type="text/x-coffeescript" class="test" id="compilation">
# Compilation
# -----------

# Helper to pipe the CoffeeScript compiler’s output through a transpiler.
transpile = (method, code, options = {}) ->
  # `method` should be 'compile' or 'eval' or 'run'
  options.bare = yes
  options.transpile =
    # Target Internet Explorer 6, which supports no ES2015+ features.
    presets: [['@babel/env', {targets: browsers: ['ie 6']}]]
  CoffeeScript[method] code, options


test "ensure that carriage returns don't break compilation on Windows", ->
  doesNotThrowCompileError 'one\r\ntwo', bare: on

test "#3089 - don't mutate passed in options to compile", ->
  opts = {}
  CoffeeScript.compile '1 + 1', opts
  ok !opts.scope

test "--bare", ->
  eq -1, CoffeeScript.compile('x = y', bare: on).indexOf 'function'
  ok 'passed' is CoffeeScript.eval '"passed"', bare: on, filename: 'test'

test "header (#1778)", ->
  header = "// Generated by CoffeeScript #{CoffeeScript.VERSION}\n"
  eq 0, CoffeeScript.compile('x = y', header: on).indexOf header

test "header is disabled by default", ->
  header = "// Generated by CoffeeScript #{CoffeeScript.VERSION}\n"
  eq -1, CoffeeScript.compile('x = y').indexOf header

test "multiple generated references", ->
  a = {b: []}
  a.b[true] = -> this == a.b
  c = 0
  d = []
  ok a.b[0<++c<2] d...

test "splat on a line by itself is invalid", ->
  throwsCompileError "x 'a'\n...\n"

test "Issue 750", ->

  throwsCompileError 'f(->'

  throwsCompileError 'a = (break)'

  throwsCompileError 'a = (return 5 for item in list)'

  throwsCompileError 'a = (return 5 while condition)'

  throwsCompileError 'a = for x in y\n  return 5'

test "Issue #986: Unicode identifiers", ->
  λ = 5
  eq λ, 5

test "#2516: Unicode spaces should not be part of identifiers", ->
  a = (x) -> x * 2
  b = 3
  eq 6, a b # U+00A0 NO-BREAK SPACE
  eq 6, a b # U+1680 OGHAM SPACE MARK
  eq 6, a b # U+2000 EN QUAD
  eq 6, a b # U+2001 EM QUAD
  eq 6, a b # U+2002 EN SPACE
  eq 6, a b # U+2003 EM SPACE
  eq 6, a b # U+2004 THREE-PER-EM SPACE
  eq 6, a b # U+2005 FOUR-PER-EM SPACE
  eq 6, a b # U+2006 SIX-PER-EM SPACE
  eq 6, a b # U+2007 FIGURE SPACE
  eq 6, a b # U+2008 PUNCTUATION SPACE
  eq 6, a b # U+2009 THIN SPACE
  eq 6, a b # U+200A HAIR SPACE
  eq 6, a b # U+202F NARROW NO-BREAK SPACE
  eq 6, a b # U+205F MEDIUM MATHEMATICAL SPACE
  eq 6, a　b # U+3000 IDEOGRAPHIC SPACE

  # #3560: Non-breaking space (U+00A0) (before `'c'`)
  eq 5, {c: 5}[ 'c' ]

  # A line where every space in non-breaking
  eq 1 + 1, 2  

test "don't accidentally stringify keywords", ->
  ok (-> this == 'this')() is false

test "#1026: no if/else/else allowed", ->
  throwsCompileError '''
    if a
      b
    else
      c
    else
      d
  '''

test "#1050: no closing asterisk comments from within block comments", ->
  throwsCompileError "### */ ###"

test "#1273: escaping quotes at the end of heredocs", ->
  throwsCompileError '"""\\"""' # """\"""
  throwsCompileError '"""\\\\\\"""' # """\\\"""

test "#1106: __proto__ compilation", ->
  object = eq
  @["__proto__"] = true
  ok __proto__

test "reference named hasOwnProperty", ->
  CoffeeScript.compile 'hasOwnProperty = 0; a = 1'

test "#1055: invalid keys in real (but not work-product) objects", ->
  throwsCompileError "@key: value"

test "#1066: interpolated strings are not implicit functions", ->
  throwsCompileError '"int#{er}polated" arg'

test "#2846: while with empty body", ->
  CoffeeScript.compile 'while 1 then', {sourceMap: true}

test "#2944: implicit call with a regex argument", ->
  CoffeeScript.compile 'o[key] /regex/'

test "#3001: `own` shouldn't be allowed in a `for`-`in` loop", ->
  throwsCompileError "a for own b in c"

test "#2994: single-line `if` requires `then`", ->
  throwsCompileError "if b else x"

test "transpile option, for Node API CoffeeScript.compile", ->
  return if global.testingBrowser
  ok transpile('compile', "import fs from 'fs'").includes 'require'

test "transpile option, for Node API CoffeeScript.eval", ->
  return if global.testingBrowser
  ok transpile 'eval', "import path from 'path'; path.sep in ['/', '\\\\']"

test "transpile option, for Node API CoffeeScript.run", ->
  return if global.testingBrowser
  doesNotThrow -> transpile 'run', "import fs from 'fs'"

test "transpile option has merged source maps", ->
  return if global.testingBrowser
  untranspiledOutput = CoffeeScript.compile "import path from 'path'\nconsole.log path.sep", sourceMap: yes
  transpiledOutput   = transpile 'compile', "import path from 'path'\nconsole.log path.sep", sourceMap: yes
  untranspiledOutput.v3SourceMap = JSON.parse untranspiledOutput.v3SourceMap
  transpiledOutput.v3SourceMap   = JSON.parse transpiledOutput.v3SourceMap
  ok untranspiledOutput.v3SourceMap.mappings isnt transpiledOutput.v3SourceMap.mappings
  # Babel adds `'use strict';` to the top of files with the modules transform.
  eq transpiledOutput.js.indexOf('use strict'), 1
  # The `'use strict';` followed by two newlines results in the first two lines
  # of the source map mappings being two blank/skipped lines.
  eq transpiledOutput.v3SourceMap.mappings.indexOf(';;'), 0
  # The number of lines in the transpiled code should match the number of lines
  # in the source map.
  eq transpiledOutput.js.split('\n').length, transpiledOutput.v3SourceMap.mappings.split(';').length

test "using transpile from the Node API requires an object", ->
  try
    CoffeeScript.compile '', transpile: yes
  catch exception
    eq exception.message, 'The transpile option must be given an object with options to pass to Babel'

test "transpile option applies to imported .coffee files", ->
  return if global.testingBrowser
  doesNotThrow -> transpile 'run', "import { getSep } from './test/importing/transpile_import'\ngetSep()"

test "#3306: trailing comma in a function call in the last line", ->
  eqJS '''
  foo bar,
  ''', '''
  foo(bar);
  '''

</script>
<script type="text/x-coffeescript" class="test" id="comprehensions">
# Comprehensions
# --------------

# * Array Comprehensions
# * Range Comprehensions
# * Object Comprehensions
# * Implicit Destructuring Assignment
# * Comprehensions with Nonstandard Step

# TODO: refactor comprehension tests

test "Basic array comprehensions.", ->

  nums    = (n * n for n in [1, 2, 3] when n & 1)
  results = (n * 2 for n in nums)

  ok results.join(',') is '2,18'


test "Basic object comprehensions.", ->

  obj   = {one: 1, two: 2, three: 3}
  names = (prop + '!' for prop of obj)
  odds  = (prop + '!' for prop, value of obj when value & 1)

  ok names.join(' ') is "one! two! three!"
  ok odds.join(' ')  is "one! three!"


test "Basic range comprehensions.", ->

  nums = (i * 3 for i in [1..3])

  negs = (x for x in [-20..-5*2])
  negs = negs[0..2]

  result = nums.concat(negs).join(', ')

  ok result is '3, 6, 9, -20, -19, -18'


test "With range comprehensions, you can loop in steps.", ->

  results = (x for x in [0...15] by 5)
  ok results.join(' ') is '0 5 10'

  results = (x for x in [0..100] by 10)
  ok results.join(' ') is '0 10 20 30 40 50 60 70 80 90 100'


test "And can loop downwards, with a negative step.", ->

  results = (x for x in [5..1])

  ok results.join(' ') is '5 4 3 2 1'
  ok results.join(' ') is [(10-5)..(-2+3)].join(' ')

  results = (x for x in [10..1])
  ok results.join(' ') is [10..1].join(' ')

  results = (x for x in [10...0] by -2)
  ok results.join(' ') is [10, 8, 6, 4, 2].join(' ')


test "Range comprehension gymnastics.", ->

  eq "#{i for i in [5..1]}", '5,4,3,2,1'
  eq "#{i for i in [5..-5] by -5}", '5,0,-5'

  a = 6
  b = 0
  c = -2

  eq "#{i for i in [a..b]}", '6,5,4,3,2,1,0'
  eq "#{i for i in [a..b] by c}", '6,4,2,0'


test "Multiline array comprehension with filter.", ->

  evens = for num in [1, 2, 3, 4, 5, 6] when not (num & 1)
             num *= -1
             num -= 2
             num * -1
  eq evens + '', '4,6,8'


  test "The in operator still works, standalone.", ->

    ok 2 of evens


test "all isn't reserved.", ->

  all = 1


test "Ensure that the closure wrapper preserves local variables.", ->

  obj = {}

  for method in ['one', 'two', 'three'] then do (method) ->
    obj[method] = ->
      "I'm " + method

  ok obj.one()   is "I'm one"
  ok obj.two()   is "I'm two"
  ok obj.three() is "I'm three"


test "Index values at the end of a loop.", ->

  i = 0
  for i in [1..3]
    -> 'func'
    break if false
  ok i is 4


test "Ensure that local variables are closed over for range comprehensions.", ->

  funcs = for i in [1..3]
    do (i) ->
      -> -i

  eq (func() for func in funcs).join(' '), '-1 -2 -3'
  ok i is 4


test "Even when referenced in the filter.", ->

  list = ['one', 'two', 'three']

  methods = for num, i in list when num isnt 'two' and i isnt 1
    do (num, i) ->
      -> num + ' ' + i

  ok methods.length is 2
  ok methods[0]() is 'one 0'
  ok methods[1]() is 'three 2'


test "Even a convoluted one.", ->

  funcs = []

  for i in [1..3]
    do (i) ->
      x = i * 2
      ((z)->
        funcs.push -> z + ' ' + i
      )(x)

  ok (func() for func in funcs).join(', ') is '2 1, 4 2, 6 3'

  funcs = []

  results = for i in [1..3]
    do (i) ->
      z = (x * 3 for x in [1..i])
      ((a, b, c) -> [a, b, c].join(' ')).apply this, z

  ok results.join(', ') is '3  , 3 6 , 3 6 9'


test "Naked ranges are expanded into arrays.", ->

  array = [0..10]
  ok(num % 2 is 0 for num in array by 2)


test "Nested shared scopes.", ->

  foo = ->
    for i in [0..7]
      do (i) ->
        for j in [0..7]
          do (j) ->
            -> i + j

  eq foo()[3][4](), 7


test "Scoped loop pattern matching.", ->

  a = [[0], [1]]
  funcs = []

  for [v] in a
    do (v) ->
      funcs.push -> v

  eq funcs[0](), 0
  eq funcs[1](), 1


test "Nested comprehensions.", ->

  multiLiner =
    for x in [3..5]
      for y in [3..5]
        [x, y]

  singleLiner =
    (([x, y] for y in [3..5]) for x in [3..5])

  ok multiLiner.length is singleLiner.length
  ok 5 is multiLiner[2][2][1]
  ok 5 is singleLiner[2][2][1]


test "Comprehensions within parentheses.", ->

  result = null
  store = (obj) -> result = obj
  store (x * 2 for x in [3, 2, 1])

  ok result.join(' ') is '6 4 2'


test "Closure-wrapped comprehensions that refer to the 'arguments' object.", ->

  expr = ->
    result = (item * item for item in arguments)

  ok expr(2, 4, 8).join(' ') is '4 16 64'


test "Fast object comprehensions over all properties, including prototypal ones.", ->

  class Cat
    constructor: -> @name = 'Whiskers'
    breed: 'tabby'
    hair:  'cream'

  whiskers = new Cat
  own = (value for own key, value of whiskers)
  all = (value for key, value of whiskers)

  ok own.join(' ') is 'Whiskers'
  ok all.sort().join(' ') is 'Whiskers cream tabby'


test "Optimized range comprehensions.", ->

  exxes = ('x' for [0...10])
  ok exxes.join(' ') is 'x x x x x x x x x x'


test "#3671: Allow step in optimized range comprehensions.", ->

  exxes = ('x' for [0...10] by 2)
  eq exxes.join(' ') , 'x x x x x'


test "#3671: Disallow guard in optimized range comprehensions.", ->

  throwsCompileError "exxes = ('x' for [0...10] when a)"


test "Loop variables should be able to reference outer variables", ->
  outer = 1
  do ->
    null for outer in [1, 2, 3]
  eq outer, 3


test "Lenient on pure statements not trying to reach out of the closure", ->

  val = for i in [1]
    for j in [] then break
    i
  ok val[0] is i


test "Comprehensions only wrap their last line in a closure, allowing other lines
  to have pure expressions in them.", ->

  func = -> for i in [1]
    break if i is 2
    j for j in [1]

  ok func()[0][0] is 1

  i = 6
  odds = while i--
    continue unless i & 1
    i

  ok odds.join(', ') is '5, 3, 1'


test "Issue #897: Ensure that plucked function variables aren't leaked.", ->

  facets = {}
  list = ['one', 'two']

  (->
    for entity in list
      facets[entity] = -> entity
  )()

  eq typeof entity, 'undefined'
  eq facets['two'](), 'two'


test "Issue #905. Soaks as the for loop subject.", ->

  a = {b: {c: [1, 2, 3]}}
  for d in a.b?.c
    e = d

  eq e, 3


test "Issue #948. Capturing loop variables.", ->

  funcs = []
  list  = ->
    [1, 2, 3]

  for y in list()
    do (y) ->
      z = y
      funcs.push -> "y is #{y} and z is #{z}"

  eq funcs[1](), "y is 2 and z is 2"


test "Cancel the comprehension if there's a jump inside the loop.", ->

  result = try
    for i in [0...10]
      continue if i < 5
    i

  eq result, 10


test "Comprehensions over break.", ->

  arrayEq (break for [1..10]), []


test "Comprehensions over continue.", ->

  arrayEq (continue for [1..10]), []


test "Comprehensions over function literals.", ->

  a = 0
  for f in [-> a = 1]
    do (f) ->
      do f

  eq a, 1


test "Comprehensions that mention arguments.", ->

  list = [arguments: 10]
  args = for f in list
    do (f) ->
      f.arguments
  eq args[0], 10


test "expression conversion under explicit returns", ->
  nonce = {}
  fn = ->
    return (nonce for x in [1,2,3])
  arrayEq [nonce,nonce,nonce], fn()
  fn = ->
    return [nonce for x in [1,2,3]][0]
  arrayEq [nonce,nonce,nonce], fn()
  fn = ->
    return [(nonce for x in [1..3])][0]
  arrayEq [nonce,nonce,nonce], fn()


test "implicit destructuring assignment in object of objects", ->
  a={}; b={}; c={}
  obj = {
    a: { d: a },
    b: { d: b }
    c: { d: c }
  }
  result = ([y,z] for y, { d: z } of obj)
  arrayEq [['a',a],['b',b],['c',c]], result


test "implicit destructuring assignment in array of objects", ->
  a={}; b={}; c={}; d={}; e={}; f={}
  arr = [
    { a: a, b: { c: b } },
    { a: c, b: { c: d } },
    { a: e, b: { c: f } }
  ]
  result = ([y,z] for { a: y, b: { c: z } } in arr)
  arrayEq [[a,b],[c,d],[e,f]], result


test "implicit destructuring assignment in array of arrays", ->
  a={}; b={}; c={}; d={}; e={}; f={}
  arr = [[a, [b]], [c, [d]], [e, [f]]]
  result = ([y,z] for [y, [z]] in arr)
  arrayEq [[a,b],[c,d],[e,f]], result

test "issue #1124: don't assign a variable in two scopes", ->
  lista = [1, 2, 3, 4, 5]
  listb = (_i + 1 for _i in lista)
  arrayEq [2, 3, 4, 5, 6], listb

test "#1326: `by` value is uncached", ->
  a = [0,1,2]
  fi = gi = hi = 0
  f = -> ++fi
  g = -> ++gi
  h = -> ++hi

  forCompile = []
  rangeCompileSimple = []

  #exercises For.compile
  for v, i in a by f()
    forCompile.push i

  #exercises Range.compileSimple
  rangeCompileSimple = (i for i in [0..2] by g())

  arrayEq a, forCompile
  arrayEq a, rangeCompileSimple
  #exercises Range.compile
  eq "#{i for i in [0..2] by h()}", '0,1,2'

test "#1669: break/continue should skip the result only for that branch", ->
  ns = for n in [0..99]
    if n > 9
      break
    else if n & 1
      continue
    else
      n
  eq "#{ns}", '0,2,4,6,8'

  # `else undefined` is implied.
  ns = for n in [1..9]
    if n % 2
      continue unless n % 5
      n
  eq "#{ns}", "1,,3,,,7,,9"

  # Ditto.
  ns = for n in [1..9]
    switch
      when n % 2
        continue unless n % 5
        n
  eq "#{ns}", "1,,3,,,7,,9"

test "#1850: inner `for` should not be expression-ized if `return`ing", ->
  eq '3,4,5', do ->
    for a in [1..9] then \
    for b in [1..9]
      c = Math.sqrt a*a + b*b
      return String [a, b, c] unless c % 1

test "#1910: loop index should be mutable within a loop iteration and immutable between loop iterations", ->
  n = 1
  iterations = 0
  arr = [0..n]
  for v, k in arr
    ++iterations
    v = k = 5
    eq 5, k
  eq 2, k
  eq 2, iterations

  iterations = 0
  for v in [0..n]
    ++iterations
  eq 2, k
  eq 2, iterations

  arr = ([v, v + 1] for v in [0..5])
  iterations = 0
  for [v0, v1], k in arr when v0
    k += 3
    ++iterations
  eq 6, k
  eq 5, iterations

test "#2007: Return object literal from comprehension", ->
  y = for x in [1, 2]
    foo: "foo" + x
  eq 2, y.length
  eq "foo1", y[0].foo
  eq "foo2", y[1].foo

  x = 2
  y = while x
    x: --x
  eq 2, y.length
  eq 1, y[0].x
  eq 0, y[1].x

test "#2274: Allow @values as loop variables", ->
  obj = {
    item: null
    method: ->
      for @item in [1, 2, 3]
        null
  }
  eq obj.item, null
  obj.method()
  eq obj.item, 3

test "#4411: Allow @values as loop indices", ->
  obj =
    index: null
    get: -> @index
    method: ->
      @get() for _, @index in [1, 2, 3]
  eq obj.index, null
  arrayEq obj.method(), [0, 1, 2]
  eq obj.index, 3

test "#2525, #1187, #1208, #1758, looping over an array forwards", ->
  list = [0, 1, 2, 3, 4]

  ident = (x) -> x

  arrayEq (i for i in list), list

  arrayEq (index for i, index in list), list

  arrayEq (i for i in list by 1), list

  arrayEq (i for i in list by ident 1), list

  arrayEq (i for i in list by ident(1) * 2), [0, 2, 4]

  arrayEq (index for i, index in list by ident(1) * 2), [0, 2, 4]

test "#2525, #1187, #1208, #1758, looping over an array backwards", ->
  list = [0, 1, 2, 3, 4]
  backwards = [4, 3, 2, 1, 0]

  ident = (x) -> x

  arrayEq (i for i in list by -1), backwards

  arrayEq (index for i, index in list by -1), backwards

  arrayEq (i for i in list by ident -1), backwards

  arrayEq (i for i in list by ident(-1) * 2), [4, 2, 0]

  arrayEq (index for i, index in list by ident(-1) * 2), [4, 2, 0]

test "splats in destructuring in comprehensions", ->
  list = [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
  arrayEq (seq for [rep, seq...] in list), [[1, 2], [3, 4], [5, 6]]

test "#156: expansion in destructuring in comprehensions", ->
  list = [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
  arrayEq (last for [..., last] in list), [2, 4, 6]

test "#3778: Consistently always cache for loop range boundaries and steps, even
      if they are simple identifiers", ->
  a = 1; arrayEq [1, 2, 3], (for n in [1, 2, 3] by  a then a = 4; n)
  a = 1; arrayEq [1, 2, 3], (for n in [1, 2, 3] by +a then a = 4; n)
  a = 1; arrayEq [1, 2, 3], (for n in [a..3]          then a = 4; n)
  a = 1; arrayEq [1, 2, 3], (for n in [+a..3]         then a = 4; n)
  a = 3; arrayEq [1, 2, 3], (for n in [1..a]          then a = 4; n)
  a = 3; arrayEq [1, 2, 3], (for n in [1..+a]         then a = 4; n)
  a = 1; arrayEq [1, 2, 3], (for n in [1..3] by  a    then a = 4; n)
  a = 1; arrayEq [1, 2, 3], (for n in [1..3] by +a    then a = 4; n)

test "for pattern variables can linebreak/indent", ->
  listOfObjects = [a: 1]
  sum = 0
  for {
    a
    somethingElse
    anotherProperty
  } in listOfObjects
    sum += a
  eq a, 1

  sum = 0
  sum += a for {
    a,
    somethingElse,
    anotherProperty,
  } in listOfObjects
  eq a, 1

  listOfArrays = [[2]]
  sum = 0
  for [
    a
    nonexistentElement
    anotherNonexistentElement
  ] in listOfArrays
    sum += a
  eq a, 2

  sum = 0
  sum += a for [
    a,
    nonexistentElement,
    anotherNonexistentElement
  ] in listOfArrays
  eq a, 2

test "#5309: comprehension as postfix condition", ->
  doesNotThrowCompileError """
    throw new Error "DOOM was called with a null element" unless elm? for elm in elms
  """

test "#5410: for from loops shouldn't make excess refs", ->
  js = CoffeeScript.compile """
    for x from fn()
      alert x
  """

  ok !js.match /ref/

</script>
<script type="text/x-coffeescript" class="test" id="control_flow">
# Control Flow
# ------------

# * Conditionals
# * Loops
#   * For
#   * While
#   * Until
#   * Loop
# * Switch
# * Throw

# TODO: make sure postfix forms and expression coercion are properly tested

# shared identity function
id = (_) -> if arguments.length is 1 then _ else Array::slice.call(arguments)

# Conditionals

test "basic conditionals", ->
  if false
    ok false
  else if false
    ok false
  else
    ok true

  if true
    ok true
  else if true
    ok false
  else
    ok true

  unless true
    ok false
  else unless true
    ok false
  else
    ok true

  unless false
    ok true
  else unless false
    ok false
  else
    ok true

test "single-line conditional", ->
  if false then ok false else ok true
  unless false then ok true else ok false

test "nested conditionals", ->
  nonce = {}
  eq nonce, (if true
    unless false
      if false then false else
        if true
          nonce)

test "nested single-line conditionals", ->
  nonce = {}

  a = if false then undefined else b = if 0 then undefined else nonce
  eq nonce, a
  eq nonce, b

  c = if false then undefined else (if 0 then undefined else nonce)
  eq nonce, c

  d = if true then id(if false then undefined else nonce)
  eq nonce, d

test "empty conditional bodies", ->
  eq undefined, (if false
  else if false
  else)

test "conditional bodies containing only comments", ->
  eq undefined, (if true
    ###
    block comment
    ###
  else
    # comment
  )

  eq undefined, (if false
    # comment
  else if true
    ###
    block comment
    ###
  else)

test "return value of if-else is from the proper body", ->
  nonce = {}
  eq nonce, if false then undefined else nonce

test "return value of unless-else is from the proper body", ->
  nonce = {}
  eq nonce, unless true then undefined else nonce

test "assign inside the condition of a conditional statement", ->
  nonce = {}
  if a = nonce then 1
  eq nonce, a
  1 if b = nonce
  eq nonce, b


# Interactions With Functions

test "single-line function definition with single-line conditional", ->
  fn = -> if 1 < 0.5 then 1 else -1
  ok fn() is -1

test "function resturns conditional value with no `else`", ->
  fn = ->
    return if false then true
  eq undefined, fn()

test "function returns a conditional value", ->
  a = {}
  fnA = ->
    return if false then undefined else a
  eq a, fnA()

  b = {}
  fnB = ->
    return unless false then b else undefined
  eq b, fnB()

test "passing a conditional value to a function", ->
  nonce = {}
  eq nonce, id if false then undefined else nonce

test "unmatched `then` should catch implicit calls", ->
  a = 0
  trueFn = -> true
  if trueFn undefined then a++
  eq 1, a


# if-to-ternary

test "if-to-ternary with instanceof requires parentheses", ->
  nonce = {}
  eq nonce, (if {} instanceof Object
    nonce
  else
    undefined)

test "if-to-ternary as part of a larger operation requires parentheses", ->
  ok 2, 1 + if false then 0 else 1


# Odd Formatting

test "if-else indented within an assignment", ->
  nonce = {}
  result =
    if false
      undefined
    else
      nonce
  eq nonce, result

test "suppressed indentation via assignment", ->
  nonce = {}
  result =
    if      false then undefined
    else if no    then undefined
    else if 0     then undefined
    else if 1 < 0 then undefined
    else               id(
         if false then undefined
         else          nonce
    )
  eq nonce, result

test "tight formatting with leading `then`", ->
  nonce = {}
  eq nonce,
  if true
  then nonce
  else undefined

test "#738: inline function defintion", ->
  nonce = {}
  fn = if true then -> nonce
  eq nonce, fn()

test "#748: trailing reserved identifiers", ->
  nonce = {}
  obj = delete: true
  result = if obj.delete
    nonce
  eq nonce, result

test 'if-else within an assignment, condition parenthesized', ->
  result = if (1 is 1) then 'correct'
  eq result, 'correct'

  result = if ('whatever' ? no) then 'correct'
  eq result, 'correct'

  f = -> 'wrong'
  result = if (f?()) then 'correct' else 'wrong'
  eq result, 'correct'

# Postfix

test "#3056: multiple postfix conditionals", ->
  temp = 'initial'
  temp = 'ignored' unless true if false
  eq temp, 'initial'

# Loops

test "basic `while` loops", ->

  i = 5
  list = while i -= 1
    i * 2
  ok list.join(' ') is "8 6 4 2"

  i = 5
  list = (i * 3 while i -= 1)
  ok list.join(' ') is "12 9 6 3"

  i = 5
  func   = (num) -> i -= num
  assert = -> ok i < 5 > 0
  results = while func 1
    assert()
    i
  ok results.join(' ') is '4 3 2 1'

  i = 10
  results = while i -= 1 when i % 2 is 0
    i * 2
  ok results.join(' ') is '16 12 8 4'


test "Issue 759: `if` within `while` condition", ->

  2 while if 1 then 0


test "assignment inside the condition of a `while` loop", ->

  nonce = {}
  count = 1
  a = nonce while count--
  eq nonce, a
  count = 1
  while count--
    b = nonce
  eq nonce, b


test "While over break.", ->

  i = 0
  result = while i < 10
    i++
    break
  arrayEq result, []


test "While over continue.", ->

  i = 0
  result = while i < 10
    i++
    continue
  arrayEq result, []


test "Basic `until`", ->

  value = false
  i = 0
  results = until value
    value = true if i is 5
    i++
  ok i is 6


test "Basic `loop`", ->

  i = 5
  list = []
  loop
    i -= 1
    break if i is 0
    list.push i * 2
  ok list.join(' ') is '8 6 4 2'


test "break at the top level", ->
  for i in [1,2,3]
    result = i
    if i == 2
      break
  eq 2, result

test "break *not* at the top level", ->
  someFunc = ->
    i = 0
    while ++i < 3
      result = i
      break if i > 1
    result
  eq 2, someFunc()

# Switch

test "basic `switch`", ->

  num = 10
  result = switch num
    when 5 then false
    when 'a'
      true
      true
      false
    when 10 then true


    # Mid-switch comment with whitespace
    # and multi line
    when 11 then false
    else false

  ok result


  func = (num) ->
    switch num
      when 2, 4, 6
        true
      when 1, 3, 5
        false

  ok func(2)
  ok func(6)
  ok !func(3)
  eq func(8), undefined


test "Ensure that trailing switch elses don't get rewritten.", ->

  result = false
  switch "word"
    when "one thing"
      doSomething()
    else
      result = true unless false

  ok result

  result = false
  switch "word"
    when "one thing"
      doSomething()
    when "other thing"
      doSomething()
    else
      result = true unless false

  ok result


test "Should be able to handle switches sans-condition.", ->

  result = switch
    when null                     then 0
    when !1                       then 1
    when '' not of {''}           then 2
    when [] not instanceof Array  then 3
    when true is false            then 4
    when 'x' < 'y' > 'z'          then 5
    when 'a' in ['b', 'c']        then 6
    when 'd' in (['e', 'f'])      then 7
    else ok

  eq result, ok


test "Should be able to use `@properties` within the switch clause.", ->

  obj = {
    num: 101
    func: ->
      switch @num
        when 101 then '101!'
        else 'other'
  }

  ok obj.func() is '101!'


test "Should be able to use `@properties` within the switch cases.", ->

  obj = {
    num: 101
    func: (yesOrNo) ->
      result = switch yesOrNo
        when yes then @num
        else 'other'
      result
  }

  ok obj.func(yes) is 101


test "Switch with break as the return value of a loop.", ->

  i = 10
  results = while i > 0
    i--
    switch i % 2
      when 1 then i
      when 0 then break

  eq results.join(', '), '9, 7, 5, 3, 1'


test "Issue #997. Switch doesn't fallthrough.", ->

  val = 1
  switch true
    when true
      if false
        return 5
    else
      val = 2

  eq val, 1

# Throw

test "Throw should be usable as an expression.", ->
  try
    false or throw 'up'
    throw new Error 'failed'
  catch e
    ok e is 'up'


test "#2555, strange function if bodies", ->
  success = -> ok true
  failure = -> ok false

  success() if do ->
    yes

  failure() if try
    false

test "#1057: `catch` or `finally` in single-line functions", ->
  ok do -> try throw 'up' catch then yes
  ok do -> try yes finally 'nothing'

test "#2367: super in for-loop", ->
  class Foo
    sum: 0
    add: (val) -> @sum += val

  class Bar extends Foo
    add: (vals...) ->
      super val for val in vals
      @sum

  eq 10, (new Bar).add 2, 3, 5

test "#4267: lots of for-loops in the same scope", ->
  # This used to include the invalid JavaScript `var do = 0`.
  code = """
    do ->
      #{Array(200).join('for [0..0] then\n  ')}
      true
  """
  ok CoffeeScript.eval(code)

# Test for issue #2342: Lexer: Inline `else` binds to wrong `if`/`switch`
test "#2343: if / then / if / then / else", ->
  a = b = yes
  c = e = g = no
  d = 1
  f = 2
  h = 3
  i = 4

  s = ->
    if a
      if b
        if c
          d
        else
          if e
            f
          else
            if g
              h
            else
              i

  t = ->
    if a then if b
      if c then d
      else if e
        f
      else if g
        h
      else
        i

  u = ->
    if a then if b
      if c then d else if e
        f
      else if g
        h
      else i

  v = ->
    if a then if b
      if c then d else if e then f
      else if g then h
      else i

  w = ->
    if a then if b
      if c then d
      else if e
          f
        else
          if g then h
          else i

  x = -> if a then if b then if c then d else if e then f else if g then h else i

  y = -> if a then if b then (if c then d else (if e then f else (if g then h else i)))

  eq 4, s()
  eq 4, t()
  eq 4, u()
  eq 4, v()
  eq 4, w()
  eq 4, x()
  eq 4, y()

  c = yes
  eq 1, s()
  eq 1, t()
  eq 1, u()
  eq 1, v()
  eq 1, w()
  eq 1, x()
  eq 1, y()

  b = no
  eq undefined, s()
  eq undefined, t()
  eq undefined, u()
  eq undefined, v()
  eq undefined, w()
  eq undefined, x()
  eq undefined, y()

test "#2343: if / then / if / then / else / else", ->
  a = b = yes
  c = e = g = no
  d = 1
  f = 2
  h = 3
  i = 4
  j = 5
  k = 6

  s = ->
    if a
      if b
        if c
          d
        else
          e
          if e
            f
          else
            if g
              h
            else
              i
      else
        j
    else
      k

  t = ->
    if a
      if b
        if c then d
        else if e
          f
        else if g
          h
        else
          i
      else
        j
    else
      k

  u = ->
    if a
      if b
        if c then d else if e
          f
        else if g
          h
        else i
      else j
    else k

  v = ->
    if a
      if b
        if c then d else if e then f
        else if g then h
        else i
      else j else k

  w = ->
    if a then if b
        if c then d
        else if e
            f
          else
            if g then h
            else i
    else j else k

  x = -> if a then if b then if c then d else if e then f else if g then h else i else j else k

  y = -> if a then (if b then (if c then d else (if e then f else (if g then h else i))) else j) else k

  eq 4, s()
  eq 4, t()
  eq 4, u()
  eq 4, v()
  eq 4, w()
  eq 4, x()
  eq 4, y()

  c = yes
  eq 1, s()
  eq 1, t()
  eq 1, u()
  eq 1, v()
  eq 1, w()
  eq 1, x()
  eq 1, y()

  b = no
  eq 5, s()
  eq 5, t()
  eq 5, u()
  eq 5, v()
  eq 5, w()
  eq 5, x()
  eq 5, y()

  a = no
  eq 6, s()
  eq 6, t()
  eq 6, u()
  eq 6, v()
  eq 6, w()
  eq 6, x()
  eq 6, y()


test "#2343: switch / when / then / if / then / else", ->
  a = b = yes
  c = e = g = no
  d = 1
  f = 2
  h = 3
  i = 4

  s = ->
    switch
      when a
        if b
          if c
            d
          else
            if e
              f
            else
              if g
                h
              else
                i


  t = ->
    switch
      when a then if b
        if c then d
        else if e
          f
        else if g
          h
        else
          i

  u = ->
    switch
      when a then if b then if c then d
      else if e then f
      else if g then h else i

  v = ->
    switch
      when a then if b then if c then d else if e then f
      else if g then h else i

  w = ->
    switch
      when a then if b then if c then d else if e then f
      else if g
        h
      else i

  x = ->
    switch
     when a then if b then if c then d else if e then f else if g then h else i

  y = -> switch
    when a then if b then (if c then d else (if e then f else (if g then h else i)))

  eq 4, s()
  eq 4, t()
  eq 4, u()
  eq 4, v()
  eq 4, w()
  eq 4, x()
  eq 4, y()

  c = yes
  eq 1, s()
  eq 1, t()
  eq 1, u()
  eq 1, v()
  eq 1, w()
  eq 1, x()
  eq 1, y()

  b = no
  eq undefined, s()
  eq undefined, t()
  eq undefined, u()
  eq undefined, v()
  eq undefined, w()
  eq undefined, x()
  eq undefined, y()

test "#2343: switch / when / then / if / then / else / else", ->
  a = b = yes
  c = e = g = no
  d = 1
  f = 2
  h = 3
  i = 4

  s = ->
    switch
      when a
        if b
          if c
            d
          else if e
            f
          else if g
            h
          else
            i
      else
        0

  t = ->
    switch
      when a
        if b
          if c then d
          else if e
            f
          else if g
            h
          else i
      else 0

  u = ->
    switch
      when a
        if b then if c
            d
          else if e
            f
          else if g
            h
          else i
      else 0

  v = ->
    switch
      when a
        if b then if c then d
        else if e
          f
        else if g
          h
        else i
      else 0

  w = ->
    switch
      when a
        if b then if c then d
        else if e then f
        else if g then h
        else i
      else 0

  x = ->
    switch
     when a
       if b then if c then d else if e then f else if g then h else i
     else 0

  y = -> switch
    when a
      if b then (if c then d else (if e then f else (if g then h else i)))
    else 0

  eq 4, s()
  eq 4, t()
  eq 4, u()
  eq 4, v()
  eq 4, w()
  eq 4, x()
  eq 4, y()

  c = yes
  eq 1, s()
  eq 1, t()
  eq 1, u()
  eq 1, v()
  eq 1, w()
  eq 1, x()
  eq 1, y()

  b = no
  eq undefined, s()
  eq undefined, t()
  eq undefined, u()
  eq undefined, v()
  eq undefined, w()
  eq undefined, x()
  eq undefined, y()

  b = yes
  a = no
  eq 0, s()
  eq 0, t()
  eq 0, u()
  eq 0, v()
  eq 0, w()
  eq 0, x()
  eq 0, y()

test "#2343: switch / when / then / if / then / else / else / else", ->
  a = b = yes
  c = e = g = no
  d = 1
  f = 2
  h = 3
  i = 4
  j = 5

  s = ->
    switch
      when a
        if b
          if c
            d
          else if e
            f
          else if g
            h
          else
            i
        else
          j
      else
        0

  t = ->
    switch
      when a
        if b
          if c then d
          else if e
            f
          else if g
            h
          else i
        else
          j
      else 0

  u = ->
    switch
      when a
        if b
          if c
            d
          else if e
            f
          else if g
            h
          else i
        else j
      else 0

  v = ->
    switch
      when a
        if b
          if c then d
          else if e
            f
          else if g then h
          else i
        else j
      else 0

  w = ->
    switch
      when a
        if b
          if c then d
          else if e then f
          else if g then h
          else i
        else j
      else 0

  x = ->
    switch
     when a
       if b then if c then d else if e then f else if g then h else i else j
     else 0

  y = -> switch
    when a
      if b then (if c then d else (if e then f else (if g then h else i))) else j
    else 0

  eq 4, s()
  eq 4, t()
  eq 4, u()
  eq 4, v()
  eq 4, w()
  eq 4, x()
  eq 4, y()

  c = yes
  eq 1, s()
  eq 1, t()
  eq 1, u()
  eq 1, v()
  eq 1, w()
  eq 1, x()
  eq 1, y()

  b = no
  eq 5, s()
  eq 5, t()
  eq 5, u()
  eq 5, v()
  eq 5, w()
  eq 5, x()
  eq 5, y()

  b = yes
  a = no
  eq 0, s()
  eq 0, t()
  eq 0, u()
  eq 0, v()
  eq 0, w()
  eq 0, x()
  eq 0, y()

# Test for issue #3921: Inline function without parentheses used in condition fails to compile
test "#3921: `if` & `unless`", ->
  a = {}
  eq a, if do -> no then undefined else a
  a1 = undefined
  if do -> yes
    a1 = a
  eq a, a1

  b = {}
  eq b, unless do -> no then b else undefined
  b1 = undefined
  unless do -> no
    b1 = b
  eq b, b1

  c = 0
  if (arg = undefined) -> yes then c++
  eq 1, c
  d = 0
  if (arg = undefined) -> yes
    d++
  eq 1, d

  answer = 'correct'
  eq answer, if do -> 'wrong' then 'correct' else 'wrong'
  eq answer, unless do -> no then 'correct' else 'wrong'
  statm1 = undefined
  if do -> 'wrong'
    statm1 = 'correct'
  eq answer, statm1
  statm2 = undefined
  unless do -> no
    statm2 = 'correct'
  eq answer, statm2

test "#3921: `post if`", ->
  a = {}
  eq a, a unless do -> no
  a1 = a if do -> yes
  eq a, a1

  c = 0
  c++ if (arg = undefined) -> yes
  eq 1, c
  d = 0
  d++ if (arg = undefined) -> yes
  eq 1, d

  answer = 'correct'
  eq answer, 'correct' if do -> 'wrong'
  eq answer, 'correct' unless do -> not 'wrong'
  statm1 = undefined
  statm1 = 'correct' if do -> 'wrong'
  eq answer, statm1
  statm2 = undefined
  statm2 = 'correct' unless do -> not 'wrong'
  eq answer, statm2

test "Issue 3921: `while` & `until`", ->
  i = 5
  assert = (a) -> ok 5 > a > 0
  result1 = while do (num = 1) -> i -= num
    assert i
    i
  ok result1.join(' ') is '4 3 2 1'

  j = 5
  result2 = until do (num = 1) -> (j -= num) < 1
    assert j
    j
  ok result2.join(' ') is '4 3 2 1'

test "#3921: `switch`", ->
  i = 1
  a = switch do (m = 2) -> i * m
    when 5 then "five"
    when 4 then "four"
    when 3 then "three"
    when 2 then "two"
    when 1 then "one"
    else "none"
  eq "two", a

  j = 12
  b = switch do (m = 3) -> j / m
    when 5 then "five"
    when 4 then "four"
    when 3 then "three"
    when 2 then "two"
    when 1 then "one"
    else "none"
  eq "four", b

  k = 20
  c = switch do (m = 4) -> k / m
    when 5 then "five"
    when 4 then "four"
    when 3 then "three"
    when 2 then "two"
    when 1 then "one"
    else "none"
  eq "five", c

# Issue #3909: backslash to break line in `for` loops throw syntax error
test "#3909: backslash `for own ... of`", ->

  obj = {a: 1, b: 2, c: 3}
  arr = ['a', 'b', 'c']

  x1 \
    = ( key for own key of obj )
  arrayEq x1, arr

  x2 = \
    ( key for own key of obj )
  arrayEq x2, arr

  x3 = ( \
    key for own key of obj )
  arrayEq x3, arr

  x4 = ( key \
    for own key of obj )
  arrayEq x4, arr

  x5 = ( key for own key of \
    obj )
  arrayEq x5, arr

  x6 = ( key for own key of obj \
    )
  arrayEq x6, arr

  x7 = ( key for \
    own key of obj )
  arrayEq x7, arr

  x8 = ( key for own \
    key of obj )
  arrayEq x8, arr

  x9 = ( key for own key \
    of obj )
  arrayEq x9, arr


test "#3909: backslash `for ... of`", ->
  obj = {a: 1, b: 2, c: 3}
  arr = ['a', 'b', 'c']

  x1 \
    = ( key for key of obj )
  arrayEq x1, arr

  x2 = \
    ( key for key of obj )
  arrayEq x2, arr

  x3 = ( \
    key for key of obj )
  arrayEq x3, arr

  x4 = ( key \
    for key of obj )
  arrayEq x4, arr

  x5 = ( key for key of \
    obj )
  arrayEq x5, arr

  x6 = ( key for key of obj \
    )
  arrayEq x6, arr

  x7 = ( key for \
    key of obj )
  arrayEq x7, arr

  x8 = ( key for key \
    of obj )
  arrayEq x8, arr


test "#3909: backslash `for ... in`", ->
  arr = ['a', 'b', 'c']

  x1 \
    = ( key for key in arr )
  arrayEq x1, arr

  x2 = \
    ( key for key in arr )
  arrayEq x2, arr

  x3 = ( \
    key for key in arr )
  arrayEq x3, arr

  x4 = ( key \
    for key in arr )
  arrayEq x4, arr

  x5 = ( key for key in \
    arr )
  arrayEq x5, arr

  x6 = ( key for key in arr \
    )
  arrayEq x6, arr

  x7 = ( key for \
    key in arr )
  arrayEq x7, arr

  x8 = ( key for key \
    in arr )
  arrayEq x8, arr

test "#4871: `else if` no longer output together ", ->
   eqJS '''
   if a then b else if c then d else if e then f else g
   ''',
   '''
   if (a) {
     b;
   } else if (c) {
     d;
   } else if (e) {
     f;
   } else {
     g;
   }
   '''

   eqJS '''
   if no
     1
   else if yes
     2
   ''',
   '''
   if (false) {
     1;
   } else if (true) {
     2;
   }
   '''

test "#4898: Lexer: backslash line continuation is inconsistent", ->
  if ( \
      false \
      or \
      true \
    )
    a = 42

  eq a, 42

  if ( \
      false \
      or \
      true \
  )
    b = 42

  eq b, 42

  if ( \
            false \
         or \
   true \
  )
    c = 42

  eq c, 42

  if \
   false \
        or \
   true
    d = 42

  eq d, 42

  if \
              false or \
  true
    e = 42

  eq e, 42

  if \
       false or \
    true \
       then \
   f = 42 \
   else
     f = 24

  eq f, 42

</script>
<script type="text/x-coffeescript" class="test" id="error_messages">
# Error Formatting
# ----------------

# Ensure that errors of different kinds (lexer, parser and compiler) are shown
# in a consistent way.

errCallback = (expectedErrorFormat) -> (err) ->
  err.colorful = no
  eq expectedErrorFormat, "#{err}"
  yes
assertErrorFormatNoAst = (code, expectedErrorFormat) ->
  throws (-> CoffeeScript.run code), errCallback(expectedErrorFormat)
assertErrorFormat = (code, expectedErrorFormat) ->
  assertErrorFormatNoAst code, expectedErrorFormat
  throws (-> CoffeeScript.compile code, ast: yes), errCallback(expectedErrorFormat)

test "lexer errors formatting", ->
  assertErrorFormat '''
    normalObject    = {}
    insideOutObject = }{
  ''',
  '''
    [stdin]:2:19: error: unmatched }
    insideOutObject = }{
                      ^
  '''

test "parser error formatting", ->
  assertErrorFormat '''
    foo in bar or in baz
  ''',
  '''
    [stdin]:1:15: error: unexpected in
    foo in bar or in baz
                  ^^
  '''

test "compiler error formatting", ->
  assertErrorFormat '''
    evil = (foo, eval, bar) ->
  ''',
  '''
    [stdin]:1:14: error: 'eval' can't be assigned
    evil = (foo, eval, bar) ->
                 ^^^^
  '''

if require?
  os   = require 'os'
  fs   = require 'fs'
  path = require 'path'

  test "patchStackTrace line patching", ->
    err = new Error 'error'
    ok err.stack.match /test[\/\\]error_messages\.coffee:\d+:\d+\b/

  test "patchStackTrace stack prelude consistent with V8", ->
    err = new Error
    ok err.stack.match /^Error\n/ # Notice no colon when no message.

    err = new Error 'error'
    ok err.stack.match /^Error: error\n/

  test "#2849: compilation error in a require()d file", ->
    # Create a temporary file to require().
    tempFile = path.join os.tmpdir(), 'syntax-error.coffee'
    ok not fs.existsSync tempFile
    fs.writeFileSync tempFile, 'foo in bar or in baz'

    try
      assertErrorFormatNoAst """
        require '#{tempFile.replace /\\/g, '\\\\'}'
      """,
      """
        #{fs.realpathSync tempFile}:1:15: error: unexpected in
        foo in bar or in baz
                      ^^
      """
    finally
      fs.unlinkSync tempFile

  test "#3890: Error.prepareStackTrace doesn't throw an error if a compiled file is deleted", ->
    # Adapted from https://github.com/atom/coffee-cash/blob/master/spec/coffee-cash-spec.coffee
    filePath = path.join os.tmpdir(), 'PrepareStackTraceTestFile.coffee'
    fs.writeFileSync filePath, "module.exports = -> throw new Error('hello world')"
    throwsAnError = require filePath
    fs.unlinkSync filePath

    try
      throwsAnError()
    catch error

    eq error.message, 'hello world'
    doesNotThrow(-> error.stack)
    notEqual error.stack.toString().indexOf(filePath), -1, "Expected " + filePath + "in stack trace: " + error.stack.toString()

  test "#4418: stack traces for compiled files reference the correct line number", ->
    # The browser is already compiling other anonymous scripts (the tests)
    # which will conflict.
    return if global.testingBrowser
    filePath = path.join os.tmpdir(), 'StackTraceLineNumberTestFile.coffee'
    fileContents = """
      testCompiledFileStackTraceLineNumber = ->
        # `a` on the next line is undefined and should throw a ReferenceError
        console.log a if true

      do testCompiledFileStackTraceLineNumber
      """
    fs.writeFileSync filePath, fileContents

    try
      require filePath
    catch error
    fs.unlinkSync filePath

    # Make sure the line number reported is line 3 (the original Coffee source)
    # and not line 6 (the generated JavaScript).
    eq /StackTraceLineNumberTestFile.coffee:(\d)/.exec(error.stack.toString())[1], '3'


test "#4418: stack traces for compiled strings reference the correct line number", ->
  # The browser is already compiling other anonymous scripts (the tests)
  # which will conflict.
  return if global.testingBrowser
  try
    CoffeeScript.run '''
      testCompiledStringStackTraceLineNumber = ->
        # `a` on the next line is undefined and should throw a ReferenceError
        console.log a if true

      do testCompiledStringStackTraceLineNumber
      '''
  catch error

  # Make sure the line number reported is line 3 (the original Coffee source)
  # and not line 6 (the generated JavaScript).
  eq /testCompiledStringStackTraceLineNumber.*:(\d):/.exec(error.stack.toString())[1], '3'


test "#4558: compiling a string inside a script doesn’t screw up stack trace line number", ->
  # The browser is already compiling other anonymous scripts (the tests)
  # which will conflict.
  return if global.testingBrowser
  try
    CoffeeScript.run '''
      testCompilingInsideAScriptDoesntScrewUpStackTraceLineNumber = ->
        if require?
          CoffeeScript = require './lib/coffeescript'
          CoffeeScript.compile ''
        throw new Error 'Some Error'

      do testCompilingInsideAScriptDoesntScrewUpStackTraceLineNumber
      '''
  catch error

  # Make sure the line number reported is line 5 (the original Coffee source)
  # and not line 10 (the generated JavaScript).
  eq /testCompilingInsideAScriptDoesntScrewUpStackTraceLineNumber.*:(\d):/.exec(error.stack.toString())[1], '5'

test "#1096: unexpected generated tokens", ->
  # Implicit ends
  assertErrorFormat 'a:, b', '''
    [stdin]:1:3: error: unexpected ,
    a:, b
      ^
  '''
  # Explicit ends
  assertErrorFormat '(a:)', '''
    [stdin]:1:4: error: unexpected )
    (a:)
       ^
  '''
  # Unexpected end of file
  assertErrorFormat 'a:', '''
    [stdin]:1:3: error: unexpected end of input
    a:
      ^
  '''
  assertErrorFormat 'a +', '''
    [stdin]:1:4: error: unexpected end of input
    a +
       ^
  '''
  # Unexpected key in implicit object (an implicit object itself is _not_
  # unexpected here)
  assertErrorFormat '''
    for i in [1]:
      1
  ''', '''
    [stdin]:2:4: error: unexpected end of input
      1
       ^
  '''
  # Unexpected regex
  assertErrorFormat '{/a/i: val}', '''
    [stdin]:1:2: error: unexpected regex
    {/a/i: val}
     ^^^^
  '''
  assertErrorFormat '{///a///i: val}', '''
    [stdin]:1:2: error: unexpected regex
    {///a///i: val}
     ^^^^^^^^
  '''
  assertErrorFormat '{///#{a}///i: val}', '''
    [stdin]:1:2: error: unexpected regex
    {///#{a}///i: val}
     ^^^^^^^^^^^
  '''
  # Unexpected string
  assertErrorFormat 'import foo from "lib-#{version}"', '''
    [stdin]:1:17: error: the name of the module to be imported from must be an uninterpolated string
    import foo from "lib-#{version}"
                    ^^^^^^^^^^^^^^^^
  '''

  # Unexpected number
  assertErrorFormat '"a"0x00Af2', '''
    [stdin]:1:4: error: unexpected number
    "a"0x00Af2
       ^^^^^^^
  '''

test "#1316: unexpected end of interpolation", ->
  assertErrorFormat '''
    "#{+}"
  ''', '''
    [stdin]:1:5: error: unexpected end of interpolation
    "#{+}"
        ^
  '''
  assertErrorFormat '''
    "#{++}"
  ''', '''
    [stdin]:1:6: error: unexpected end of interpolation
    "#{++}"
         ^
  '''
  assertErrorFormat '''
    "#{-}"
  ''', '''
    [stdin]:1:5: error: unexpected end of interpolation
    "#{-}"
        ^
  '''
  assertErrorFormat '''
    "#{--}"
  ''', '''
    [stdin]:1:6: error: unexpected end of interpolation
    "#{--}"
         ^
  '''
  assertErrorFormat '''
    "#{~}"
  ''', '''
    [stdin]:1:5: error: unexpected end of interpolation
    "#{~}"
        ^
  '''
  assertErrorFormat '''
    "#{!}"
  ''', '''
    [stdin]:1:5: error: unexpected end of interpolation
    "#{!}"
        ^
  '''
  assertErrorFormat '''
    "#{not}"
  ''', '''
    [stdin]:1:7: error: unexpected end of interpolation
    "#{not}"
          ^
  '''
  assertErrorFormat '''
    "#{5) + (4}_"
  ''', '''
    [stdin]:1:5: error: unmatched )
    "#{5) + (4}_"
        ^
  '''
  # #2918
  assertErrorFormat '''
    "#{foo.}"
  ''', '''
    [stdin]:1:8: error: unexpected end of interpolation
    "#{foo.}"
           ^
  '''

test "#3325: implicit indentation errors", ->
  assertErrorFormat '''
    i for i in a then i
  ''', '''
    [stdin]:1:14: error: unexpected then
    i for i in a then i
                 ^^^^
  '''

test "explicit indentation errors", ->
  assertErrorFormat '''
    a = b
      c
  ''', '''
    [stdin]:2:1: error: unexpected indentation
      c
    ^^
  '''

test "unclosed strings", ->
  assertErrorFormat '''
    '
  ''', '''
    [stdin]:1:1: error: missing '
    '
    ^
  '''
  assertErrorFormat '''
    "
  ''', '''
    [stdin]:1:1: error: missing "
    "
    ^
  '''
  assertErrorFormat """
    '''
  """, """
    [stdin]:1:1: error: missing '''
    '''
    ^^^
  """
  assertErrorFormat '''
    """
  ''', '''
    [stdin]:1:1: error: missing """
    """
    ^^^
  '''
  assertErrorFormat '''
    "#{"
  ''', '''
    [stdin]:1:4: error: missing "
    "#{"
       ^
  '''
  assertErrorFormat '''
    """#{"
  ''', '''
    [stdin]:1:6: error: missing "
    """#{"
         ^
  '''
  assertErrorFormat '''
    "#{"""
  ''', '''
    [stdin]:1:4: error: missing """
    "#{"""
       ^^^
  '''
  assertErrorFormat '''
    """#{"""
  ''', '''
    [stdin]:1:6: error: missing """
    """#{"""
         ^^^
  '''
  assertErrorFormat '''
    ///#{"""
  ''', '''
    [stdin]:1:6: error: missing """
    ///#{"""
         ^^^
  '''
  assertErrorFormat '''
    "a
      #{foo """
        bar
          #{ +'12 }
        baz
        """} b"
  ''', '''
    [stdin]:4:11: error: missing '
          #{ +'12 }
              ^
  '''
  # https://github.com/jashkenas/coffeescript/issues/3301#issuecomment-31735168
  assertErrorFormat '''
    # Note the double escaping; this would be `"""a\"""` real code.
    """a\\"""
  ''', '''
    [stdin]:2:1: error: missing """
    """a\\"""
    ^^^
  '''

test "unclosed heregexes", ->
  assertErrorFormat '''
    ///
  ''', '''
    [stdin]:1:1: error: missing ///
    ///
    ^^^
  '''
  # https://github.com/jashkenas/coffeescript/issues/3301#issuecomment-31735168
  assertErrorFormat '''
    # Note the double escaping; this would be `///a\///` real code.
    ///a\\///
  ''', '''
    [stdin]:2:1: error: missing ///
    ///a\\///
    ^^^
  '''

test "unexpected token after string", ->
  # Parsing error.
  assertErrorFormat '''
    'foo'bar
  ''', '''
    [stdin]:1:6: error: unexpected identifier
    'foo'bar
         ^^^
  '''
  assertErrorFormat '''
    "foo"bar
  ''', '''
    [stdin]:1:6: error: unexpected identifier
    "foo"bar
         ^^^
  '''
  # Lexing error.
  assertErrorFormat '''
    'foo'bar'
  ''', '''
    [stdin]:1:9: error: missing '
    'foo'bar'
            ^
  '''
  assertErrorFormat '''
    "foo"bar"
  ''', '''
    [stdin]:1:9: error: missing "
    "foo"bar"
            ^
  '''

test "#3348: Location data is wrong in interpolations with leading whitespace", ->
  assertErrorFormat '''
    "#{ * }"
  ''', '''
    [stdin]:1:5: error: unexpected *
    "#{ * }"
        ^
  '''

test "octal escapes", ->
  assertErrorFormat '''
    "a\\0\\tb\\\\\\07c"
  ''', '''
    [stdin]:1:10: error: octal escape sequences are not allowed \\07
    "a\\0\\tb\\\\\\07c"
      \  \   \ \ ^\^^
  '''
  assertErrorFormat '''
    "a
      #{b} \\1"
  ''', '''
    [stdin]:2:8: error: octal escape sequences are not allowed \\1
      #{b} \\1"
           ^\^
  '''
  assertErrorFormat '''
    /a\\0\\tb\\\\\\07c/
  ''', '''
    [stdin]:1:10: error: octal escape sequences are not allowed \\07
    /a\\0\\tb\\\\\\07c/
      \  \   \ \ ^\^^
  '''
  assertErrorFormat '''
    /a\\1\\tb\\\\\\07c/
  ''', '''
    [stdin]:1:10: error: octal escape sequences are not allowed \\07
    /a\\1\\tb\\\\\\07c/
      \  \   \ \ ^\^^
  '''
  assertErrorFormat '''
    ///a
      #{b} \\01///
  ''', '''
    [stdin]:2:8: error: octal escape sequences are not allowed \\01
      #{b} \\01///
           ^\^^
  '''
  # per #5211, also treat \0[8-9] as (disallowed) octal escapes
  assertErrorFormat '''
    "a\\0\\tb\\\\\\09c"
  ''', '''
    [stdin]:1:10: error: octal escape sequences are not allowed \\09
    "a\\0\\tb\\\\\\09c"
      \  \   \ \ ^\^^
  '''
  assertErrorFormat '''
    ///a
      #{b} \\08///
  ''', '''
    [stdin]:2:8: error: octal escape sequences are not allowed \\08
      #{b} \\08///
           ^\^^
  '''

test "#3795: invalid escapes", ->
  assertErrorFormat '''
    "a\\0\\tb\\\\\\x7g"
  ''', '''
    [stdin]:1:10: error: invalid escape sequence \\x7g
    "a\\0\\tb\\\\\\x7g"
      \  \   \ \ ^\^^^
  '''
  assertErrorFormat '''
    "a
      #{b} \\uA02
     c"
  ''', '''
    [stdin]:2:8: error: invalid escape sequence \\uA02
      #{b} \\uA02
           ^\^^^^
  '''
  assertErrorFormat '''
    /a\\u002space/
  ''', '''
    [stdin]:1:3: error: invalid escape sequence \\u002s
    /a\\u002space/
      ^\^^^^^
  '''
  assertErrorFormat '''
    ///a \\u002 0 space///
  ''', '''
    [stdin]:1:6: error: invalid escape sequence \\u002 \n\
    ///a \\u002 0 space///
         ^\^^^^^
  '''
  assertErrorFormat '''
    ///a
      #{b} \\x0
     c///
  ''', '''
    [stdin]:2:8: error: invalid escape sequence \\x0
      #{b} \\x0
           ^\^^
  '''
  assertErrorFormat '''
    /ab\\u/
  ''', '''
    [stdin]:1:4: error: invalid escape sequence \\u
    /ab\\u/
       ^\^
  '''

test "illegal herecomment", ->
  assertErrorFormat '''
    ###
      Regex: /a*/g
    ###
  ''', '''
    [stdin]:2:12: error: block comments cannot contain */
      Regex: /a*/g
               ^^
  '''

test "#1724: regular expressions beginning with *", ->
  assertErrorFormat '''
    /* foo/
  ''', '''
    [stdin]:1:2: error: regular expressions cannot begin with *
    /* foo/
     ^
  '''
  assertErrorFormat '''
    ///
      * foo
    ///
  ''', '''
    [stdin]:2:3: error: regular expressions cannot begin with *
      * foo
      ^
  '''

test "invalid regex flags", ->
  assertErrorFormat '''
    /a/ii
  ''', '''
    [stdin]:1:4: error: invalid regular expression flags ii
    /a/ii
       ^^
  '''
  assertErrorFormat '''
    /a/G
  ''', '''
    [stdin]:1:4: error: invalid regular expression flags G
    /a/G
       ^
  '''
  assertErrorFormat '''
    /a/gimi
  ''', '''
    [stdin]:1:4: error: invalid regular expression flags gimi
    /a/gimi
       ^^^^
  '''
  assertErrorFormat '''
    /a/g_
  ''', '''
    [stdin]:1:4: error: invalid regular expression flags g_
    /a/g_
       ^^
  '''
  assertErrorFormat '''
    ///a///ii
  ''', '''
    [stdin]:1:8: error: invalid regular expression flags ii
    ///a///ii
           ^^
  '''
  doesNotThrowCompileError '/a/ymgi'

test "missing `)`, `}`, `]`", ->
  assertErrorFormat '''
    (
  ''', '''
    [stdin]:1:1: error: missing )
    (
    ^
  '''
  assertErrorFormat '''
    {
  ''', '''
    [stdin]:1:1: error: missing }
    {
    ^
  '''
  assertErrorFormat '''
    [
  ''', '''
    [stdin]:1:1: error: missing ]
    [
    ^
  '''
  assertErrorFormat '''
    obj = {a: [1, (2+
  ''', '''
    [stdin]:1:15: error: missing )
    obj = {a: [1, (2+
                  ^
  '''
  assertErrorFormat '''
    "#{
  ''', '''
    [stdin]:1:3: error: missing }
    "#{
      ^
  '''
  assertErrorFormat '''
    """
      foo#{ bar "#{1}"
  ''', '''
    [stdin]:2:7: error: missing }
      foo#{ bar "#{1}"
          ^
  '''

test "unclosed regexes", ->
  assertErrorFormat '''
    /
  ''', '''
    [stdin]:1:1: error: missing / (unclosed regex)
    /
    ^
  '''
  assertErrorFormat '''
    # Note the double escaping; this would be `/a\/` real code.
    /a\\/
  ''', '''
    [stdin]:2:1: error: missing / (unclosed regex)
    /a\\/
    ^
  '''
  assertErrorFormat '''
    /// ^
      a #{""" ""#{if /[/].test "|" then 1 else 0}"" """}
    ///
  ''', '''
    [stdin]:2:18: error: missing / (unclosed regex)
      a #{""" ""#{if /[/].test "|" then 1 else 0}"" """}
                     ^
  '''

test "duplicate function arguments", ->
  assertErrorFormat '''
    (foo, bar, foo) ->
  ''', '''
    [stdin]:1:12: error: multiple parameters named 'foo'
    (foo, bar, foo) ->
               ^^^
  '''
  assertErrorFormat '''
    (@foo, bar, @foo) ->
  ''', '''
    [stdin]:1:13: error: multiple parameters named '@foo'
    (@foo, bar, @foo) ->
                ^^^^
  '''

test "reserved words", ->
  assertErrorFormat '''
    case
  ''', '''
    [stdin]:1:1: error: reserved word 'case'
    case
    ^^^^
  '''
  assertErrorFormat '''
    case = 1
  ''', '''
    [stdin]:1:1: error: reserved word 'case'
    case = 1
    ^^^^
  '''
  assertErrorFormat '''
    for = 1
  ''', '''
    [stdin]:1:1: error: keyword 'for' can't be assigned
    for = 1
    ^^^
  '''
  assertErrorFormat '''
    unless = 1
  ''', '''
    [stdin]:1:1: error: keyword 'unless' can't be assigned
    unless = 1
    ^^^^^^
  '''
  assertErrorFormat '''
    for += 1
  ''', '''
    [stdin]:1:1: error: keyword 'for' can't be assigned
    for += 1
    ^^^
  '''
  assertErrorFormat '''
    for &&= 1
  ''', '''
    [stdin]:1:1: error: keyword 'for' can't be assigned
    for &&= 1
    ^^^
  '''
  # Make sure token look-behind doesn't go out of range.
  assertErrorFormat '''
    &&= 1
  ''', '''
    [stdin]:1:1: error: unexpected &&=
    &&= 1
    ^^^
  '''
  # #2306: Show unaliased name in error messages.
  assertErrorFormat '''
    on = 1
  ''', '''
    [stdin]:1:1: error: keyword 'on' can't be assigned
    on = 1
    ^^
  '''

test "strict mode errors", ->
  assertErrorFormat '''
    eval = 1
  ''', '''
    [stdin]:1:1: error: 'eval' can't be assigned
    eval = 1
    ^^^^
  '''
  assertErrorFormat '''
    class eval
  ''', '''
    [stdin]:1:7: error: 'eval' can't be assigned
    class eval
          ^^^^
  '''
  assertErrorFormat '''
    arguments++
  ''', '''
    [stdin]:1:1: error: 'arguments' can't be assigned
    arguments++
    ^^^^^^^^^
  '''
  assertErrorFormat '''
    --arguments
  ''', '''
    [stdin]:1:3: error: 'arguments' can't be assigned
    --arguments
      ^^^^^^^^^
  '''

test "invalid numbers", ->
  assertErrorFormat '''
    0X0
  ''', '''
    [stdin]:1:2: error: radix prefix in '0X0' must be lowercase
    0X0
     ^
  '''
  assertErrorFormat '''
    018
  ''', '''
    [stdin]:1:1: error: decimal literal '018' must not be prefixed with '0'
    018
    ^^^
  '''
  assertErrorFormat '''
    010
  ''', '''
    [stdin]:1:1: error: octal literal '010' must be prefixed with '0o'
    010
    ^^^
'''


test "unexpected object keys", ->
  assertErrorFormat '''
    {(a + "b")}
  ''', '''
    [stdin]:1:11: error: unexpected }
    {(a + "b")}
              ^
  '''
  assertErrorFormat '''
    {(a + "b"): 1}
  ''', '''
    [stdin]:1:11: error: unexpected :
    {(a + "b"): 1}
              ^
  '''
  assertErrorFormat '''
    (a + "b"): 1
  ''', '''
    [stdin]:1:10: error: unexpected :
    (a + "b"): 1
             ^
  '''

test "invalid object keys", ->
  assertErrorFormat '''
    @a: 1
  ''', '''
    [stdin]:1:1: error: invalid object key
    @a: 1
    ^^
  '''
  assertErrorFormat '''
    f
      @a: 1
  ''', '''
    [stdin]:2:3: error: invalid object key
      @a: 1
      ^^
  '''
  assertErrorFormat '''
    {a=2}
  ''', '''
    [stdin]:1:3: error: unexpected =
    {a=2}
      ^
  '''
  assertErrorFormat '''
    @[a]: 1
  ''', '''
    [stdin]:1:1: error: invalid object key
    @[a]: 1
    ^^^^
  '''

test "invalid destructuring default target", ->
  assertErrorFormat '''
    {'a' = 2} = obj
  ''', '''
    [stdin]:1:6: error: unexpected =
    {'a' = 2} = obj
         ^
  '''

test "#4070: lone expansion", ->
  assertErrorFormat '''
    [...] = a
  ''', '''
    [stdin]:1:2: error: Destructuring assignment has no target
    [...] = a
     ^^^
  '''
  assertErrorFormat '''
    [ ..., ] = a
  ''', '''
    [stdin]:1:3: error: Destructuring assignment has no target
    [ ..., ] = a
      ^^^
  '''

test "#3926: implicit object in parameter list", ->
  assertErrorFormat '''
    (a: b) ->
  ''', '''
    [stdin]:1:3: error: unexpected :
    (a: b) ->
      ^
  '''
  assertErrorFormat '''
    (one, two, {three, four: five}, key: value) ->
  ''', '''
    [stdin]:1:36: error: unexpected :
    (one, two, {three, four: five}, key: value) ->
                                       ^
  '''

test "#4130: unassignable in destructured param", ->
  assertErrorFormat '''
    fun = ({
      @param : null
    }) ->
      console.log "Oh hello!"
  ''', '''
    [stdin]:2:12: error: keyword 'null' can't be assigned
      @param : null
               ^^^^
  '''
  assertErrorFormat '''
    ({a: null}) ->
  ''', '''
    [stdin]:1:6: error: keyword 'null' can't be assigned
    ({a: null}) ->
         ^^^^
  '''
  assertErrorFormat '''
    ({a: 1}) ->
  ''', '''
    [stdin]:1:6: error: '1' can't be assigned
    ({a: 1}) ->
         ^
  '''
  assertErrorFormat '''
    ({1}) ->
  ''', '''
    [stdin]:1:3: error: '1' can't be assigned
    ({1}) ->
      ^
  '''
  assertErrorFormat '''
    ({a: true = 1}) ->
  ''', '''
    [stdin]:1:6: error: keyword 'true' can't be assigned
    ({a: true = 1}) ->
         ^^^^
  '''

test "`yield` outside of a function", ->
  assertErrorFormat '''
    yield 1
  ''', '''
    [stdin]:1:1: error: yield can only occur inside functions
    yield 1
    ^^^^^^^
  '''
  assertErrorFormat '''
    yield return
  ''', '''
    [stdin]:1:1: error: yield can only occur inside functions
    yield return
    ^^^^^^^^^^^^
  '''

test "#4097: `yield return` as an expression", ->
  assertErrorFormat '''
    -> (yield return)
  ''', '''
    [stdin]:1:5: error: cannot use a pure statement in an expression
    -> (yield return)
        ^^^^^^^^^^^^
  '''

test "#5013: `await return` as an expression", ->
  assertErrorFormat '''
    -> (await return)
  ''', '''
    [stdin]:1:5: error: cannot use a pure statement in an expression
    -> (await return)
        ^^^^^^^^^^^^
  '''

test "#5013: `return` as an expression", ->
  assertErrorFormat '''
    -> (return)
  ''', '''
    [stdin]:1:5: error: cannot use a pure statement in an expression
    -> (return)
        ^^^^^^
  '''

test "#5013: `break` as an expression", ->
  assertErrorFormat '''
    (b = 1; break) for b in a
  ''', '''
    [stdin]:1:9: error: cannot use a pure statement in an expression
    (b = 1; break) for b in a
            ^^^^^
  '''

test "#5013: `continue` as an expression", ->
  assertErrorFormat '''
    (b = 1; continue) for b in a
  ''', '''
    [stdin]:1:9: error: cannot use a pure statement in an expression
    (b = 1; continue) for b in a
            ^^^^^^^^
  '''

test "`&&=` and `||=` with a space in-between", ->
  assertErrorFormat '''
    a = 0
    a && = 1
  ''', '''
    [stdin]:2:6: error: unexpected =
    a && = 1
         ^
  '''
  assertErrorFormat '''
    a = 0
    a and = 1
  ''', '''
    [stdin]:2:7: error: unexpected =
    a and = 1
          ^
  '''
  assertErrorFormat '''
    a = 0
    a || = 1
  ''', '''
    [stdin]:2:6: error: unexpected =
    a || = 1
         ^
  '''
  assertErrorFormat '''
    a = 0
    a or = 1
  ''', '''
    [stdin]:2:6: error: unexpected =
    a or = 1
         ^
  '''

test "anonymous functions cannot be exported", ->
  assertErrorFormat '''
    export ->
      console.log 'hello, world!'
  ''', '''
    [stdin]:1:8: error: unexpected ->
    export ->
           ^^
  '''

test "anonymous classes cannot be exported", ->
  assertErrorFormat '''
    export class
      constructor: ->
        console.log 'hello, world!'
  ''', '''
    [stdin]:1:8: error: anonymous classes cannot be exported
    export class
           ^^^^^
  '''

test "unless enclosed by curly braces, only * can be aliased", ->
  assertErrorFormat '''
    import foo as bar from 'lib'
  ''', '''
    [stdin]:1:12: error: unexpected as
    import foo as bar from 'lib'
               ^^
  '''

test "unwrapped imports must follow constrained syntax", ->
  assertErrorFormat '''
    import foo, bar from 'lib'
  ''', '''
    [stdin]:1:13: error: unexpected identifier
    import foo, bar from 'lib'
                ^^^
  '''
  assertErrorFormat '''
    import foo, bar, baz from 'lib'
  ''', '''
    [stdin]:1:13: error: unexpected identifier
    import foo, bar, baz from 'lib'
                ^^^
  '''
  assertErrorFormat '''
    import foo, bar as baz from 'lib'
  ''', '''
    [stdin]:1:13: error: unexpected identifier
    import foo, bar as baz from 'lib'
                ^^^
  '''

test "cannot export * without a module to export from", ->
  assertErrorFormat '''
    export *
  ''', '''
    [stdin]:1:9: error: unexpected end of input
    export *
            ^
  '''

test "imports and exports must be top-level", ->
  assertErrorFormatNoAst '''
    if foo
      import { bar } from 'lib'
  ''', '''
    [stdin]:2:3: error: import statements must be at top-level scope
      import { bar } from 'lib'
      ^^^^^^^^^^^^^^^^^^^^^^^^^
  '''
  assertErrorFormatNoAst '''
    foo = ->
      export { bar }
  ''', '''
    [stdin]:2:3: error: export statements must be at top-level scope
      export { bar }
      ^^^^^^^^^^^^^^
  '''

test "cannot import the same member more than once", ->
  assertErrorFormat '''
    import { foo, foo } from 'lib'
  ''', '''
    [stdin]:1:15: error: 'foo' has already been declared
    import { foo, foo } from 'lib'
                  ^^^
  '''
  assertErrorFormat '''
    import { foo, bar, foo } from 'lib'
  ''', '''
    [stdin]:1:20: error: 'foo' has already been declared
    import { foo, bar, foo } from 'lib'
                       ^^^
  '''
  assertErrorFormat '''
    import { foo, bar as foo } from 'lib'
  ''', '''
    [stdin]:1:15: error: 'foo' has already been declared
    import { foo, bar as foo } from 'lib'
                  ^^^^^^^^^^
  '''
  assertErrorFormat '''
    import foo, { foo } from 'lib'
  ''', '''
    [stdin]:1:15: error: 'foo' has already been declared
    import foo, { foo } from 'lib'
                  ^^^
  '''
  assertErrorFormat '''
    import foo, { bar as foo } from 'lib'
  ''', '''
    [stdin]:1:15: error: 'foo' has already been declared
    import foo, { bar as foo } from 'lib'
                  ^^^^^^^^^^
  '''
  assertErrorFormat '''
    import foo from 'libA'
    import foo from 'libB'
  ''', '''
    [stdin]:2:8: error: 'foo' has already been declared
    import foo from 'libB'
           ^^^
  '''
  assertErrorFormat '''
    import * as foo from 'libA'
    import { foo } from 'libB'
  ''', '''
    [stdin]:2:10: error: 'foo' has already been declared
    import { foo } from 'libB'
             ^^^
  '''

test "imported members cannot be reassigned", ->
  assertErrorFormat '''
    import { foo } from 'lib'
    foo = 'bar'
  ''', '''
    [stdin]:2:1: error: 'foo' is read-only
    foo = 'bar'
    ^^^
  '''
  assertErrorFormat '''
    import { foo } from 'lib'
    export default foo = 'bar'
  ''', '''
    [stdin]:2:16: error: 'foo' is read-only
    export default foo = 'bar'
                   ^^^
  '''
  assertErrorFormat '''
    import { foo } from 'lib'
    export foo = 'bar'
  ''', '''
    [stdin]:2:8: error: 'foo' is read-only
    export foo = 'bar'
           ^^^
  '''

test "bound functions cannot be generators", ->
  assertErrorFormat 'f = => yield this', '''
    [stdin]:1:8: error: yield cannot occur inside bound (fat arrow) functions
    f = => yield this
           ^^^^^^^^^^
  '''

test "#4790: bound functions cannot be generators, even when we’re creating IIFEs", ->
  assertErrorFormat '''
  =>
    for x in []
      for y in []
        yield z
  ''', '''
    [stdin]:4:7: error: yield cannot occur inside bound (fat arrow) functions
          yield z
          ^^^^^^^
  '''

test "CoffeeScript keywords cannot be used as unaliased names in import lists", ->
  assertErrorFormat """
    import { unless, baz as bar } from 'lib'
    bar.barMethod()
  """, '''
    [stdin]:1:10: error: unexpected unless
    import { unless, baz as bar } from 'lib'
             ^^^^^^
  '''

test "CoffeeScript keywords cannot be used as local names in import list aliases", ->
  assertErrorFormat """
    import { bar as unless, baz as bar } from 'lib'
    bar.barMethod()
  """, '''
    [stdin]:1:17: error: unexpected unless
    import { bar as unless, baz as bar } from 'lib'
                    ^^^^^^
  '''

test "cannot have `await return` outside a function", ->
  assertErrorFormat '''
    await return
  ''', '''
    [stdin]:1:1: error: await can only occur inside functions
    await return
    ^^^^^^^^^^^^
  '''

test "indexes are not supported in for-from loops", ->
  assertErrorFormat "x for x, i from [1, 2, 3]", '''
    [stdin]:1:10: error: cannot use index with for-from
    x for x, i from [1, 2, 3]
             ^
  '''

test "own is not supported in for-from loops", ->
  assertErrorFormat "x for own x from [1, 2, 3]", '''
    [stdin]:1:7: error: cannot use own with for-from
    x for own x from [1, 2, 3]
          ^^^
    '''

test "tagged template literals must be called by an identifier", ->
  assertErrorFormat "1''", '''
    [stdin]:1:1: error: literal is not a function
    1''
    ^
  '''
  assertErrorFormat '1""', '''
    [stdin]:1:1: error: literal is not a function
    1""
    ^
  '''
  assertErrorFormat "1'b'", '''
    [stdin]:1:1: error: literal is not a function
    1'b'
    ^
  '''
  assertErrorFormat '1"b"', '''
    [stdin]:1:1: error: literal is not a function
    1"b"
    ^
  '''
  assertErrorFormat "1'''b'''", """
    [stdin]:1:1: error: literal is not a function
    1'''b'''
    ^
  """
  assertErrorFormat '1"""b"""', '''
    [stdin]:1:1: error: literal is not a function
    1"""b"""
    ^
  '''
  assertErrorFormat '1"#{b}"', '''
    [stdin]:1:1: error: literal is not a function
    1"#{b}"
    ^
  '''
  assertErrorFormat '1"""#{b}"""', '''
    [stdin]:1:1: error: literal is not a function
    1"""#{b}"""
    ^
  '''

test "constructor functions can't be async", ->
  assertErrorFormat 'class then constructor: -> await x', '''
    [stdin]:1:12: error: Class constructor may not be async
    class then constructor: -> await x
               ^^^^^^^^^^^
  '''

test "constructor functions can't be generators", ->
  assertErrorFormat 'class then constructor: -> yield', '''
    [stdin]:1:12: error: Class constructor may not be a generator
    class then constructor: -> yield
               ^^^^^^^^^^^
  '''

test "non-derived constructors can't call super", ->
  assertErrorFormat 'class then constructor: -> super()', '''
    [stdin]:1:28: error: 'super' is only allowed in derived class constructors
    class then constructor: -> super()
                               ^^^^^^^
  '''

test "derived constructors can't reference `this` before calling super", ->
  assertErrorFormat 'class extends A then constructor: -> @', '''
    [stdin]:1:38: error: Can't reference 'this' before calling super in derived class constructors
    class extends A then constructor: -> @
                                         ^
  '''

test "derived constructors can't use @params without calling super", ->
  assertErrorFormat 'class extends A then constructor: (@a) ->', '''
    [stdin]:1:36: error: Can't use @params in derived class constructors without calling super
    class extends A then constructor: (@a) ->
                                       ^^
  '''

test "derived constructors can't call super with @params", ->
  assertErrorFormat 'class extends A then constructor: (@a) -> super(@a)', '''
    [stdin]:1:49: error: Can't call super with @params in derived class constructors
    class extends A then constructor: (@a) -> super(@a)
                                                    ^^
  '''

test "derived constructors can't call super with buried @params", ->
  assertErrorFormat 'class extends A then constructor: (@a) -> super((=> @a)())', '''
    [stdin]:1:53: error: Can't call super with @params in derived class constructors
    class extends A then constructor: (@a) -> super((=> @a)())
                                                        ^^
  '''

test "'super' is not allowed in constructor parameter defaults", ->
  assertErrorFormat 'class extends A then constructor: (a = super()) ->', '''
    [stdin]:1:40: error: 'super' is not allowed in constructor parameter defaults
    class extends A then constructor: (a = super()) ->
                                           ^^^^^^^
  '''

test "can't use pattern matches for loop indices", ->
  assertErrorFormat 'a for b, {c} in d', '''
    [stdin]:1:10: error: index cannot be a pattern matching expression
    a for b, {c} in d
             ^^^
  '''

test "bare 'super' is no longer allowed", ->
  # TODO Improve this error message (it should at least be 'unexpected super')
  assertErrorFormat 'class extends A then constructor: -> super', '''
    [stdin]:1:35: error: unexpected ->
    class extends A then constructor: -> super
                                      ^^
  '''

test "soaked 'super' in constructor", ->
  assertErrorFormat 'class extends A then constructor: -> super?()', '''
    [stdin]:1:38: error: Unsupported reference to 'super'
    class extends A then constructor: -> super?()
                                         ^^^^^
  '''

test "new with 'super'", ->
  assertErrorFormat 'class extends A then foo: -> new super()', '''
    [stdin]:1:34: error: Unsupported reference to 'super'
    class extends A then foo: -> new super()
                                     ^^^^^
  '''

test "'super' outside method", ->
  assertErrorFormat 'super()', '''
    [stdin]:1:1: error: cannot use super outside of an instance method
    super()
    ^^^^^
  '''

test "getter keyword in object", ->
  assertErrorFormat '''
    obj =
      get foo: ->
  ''', '''
    [stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
      get foo: ->
      ^^^
  '''

test "setter keyword in object", ->
  assertErrorFormat '''
    obj =
      set foo: ->
  ''', '''
    [stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
      set foo: ->
      ^^^
  '''

test "getter keyword in inline implicit object", ->
  assertErrorFormat 'obj = get foo: ->', '''
    [stdin]:1:7: error: 'get' cannot be used as a keyword, or as a function call without parentheses
    obj = get foo: ->
          ^^^
  '''

test "setter keyword in inline implicit object", ->
  assertErrorFormat 'obj = set foo: ->', '''
    [stdin]:1:7: error: 'set' cannot be used as a keyword, or as a function call without parentheses
    obj = set foo: ->
          ^^^
  '''

test "getter keyword in inline explicit object", ->
  assertErrorFormat 'obj = {get foo: ->}', '''
    [stdin]:1:8: error: 'get' cannot be used as a keyword, or as a function call without parentheses
    obj = {get foo: ->}
           ^^^
  '''

test "setter keyword in inline explicit object", ->
  assertErrorFormat 'obj = {set foo: ->}', '''
    [stdin]:1:8: error: 'set' cannot be used as a keyword, or as a function call without parentheses
    obj = {set foo: ->}
           ^^^
  '''

test "getter keyword in function", ->
  assertErrorFormat '''
    f = ->
      get foo: ->
  ''', '''
    [stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
      get foo: ->
      ^^^
  '''

test "setter keyword in function", ->
  assertErrorFormat '''
    f = ->
      set foo: ->
  ''', '''
    [stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
      set foo: ->
      ^^^
  '''

test "getter keyword in inline function", ->
  assertErrorFormat 'f = -> get foo: ->', '''
    [stdin]:1:8: error: 'get' cannot be used as a keyword, or as a function call without parentheses
    f = -> get foo: ->
           ^^^
  '''

test "setter keyword in inline function", ->
  assertErrorFormat 'f = -> set foo: ->', '''
    [stdin]:1:8: error: 'set' cannot be used as a keyword, or as a function call without parentheses
    f = -> set foo: ->
           ^^^
  '''

test "getter keyword in class", ->
  assertErrorFormat '''
    class A
      get foo: ->
  ''', '''
    [stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
      get foo: ->
      ^^^
  '''

test "setter keyword in class", ->
  assertErrorFormat '''
    class A
      set foo: ->
  ''', '''
    [stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
      set foo: ->
      ^^^
  '''

test "getter keyword in inline class", ->
  assertErrorFormat 'class A then get foo: ->', '''
      [stdin]:1:14: error: 'get' cannot be used as a keyword, or as a function call without parentheses
      class A then get foo: ->
                   ^^^
  '''

test "setter keyword in inline class", ->
  assertErrorFormat 'class A then set foo: ->', '''
      [stdin]:1:14: error: 'set' cannot be used as a keyword, or as a function call without parentheses
      class A then set foo: ->
                   ^^^
  '''

test "getter keyword before static method", ->
  assertErrorFormat '''
    class A
      get @foo = ->
  ''', '''
    [stdin]:2:3: error: 'get' cannot be used as a keyword, or as a function call without parentheses
      get @foo = ->
      ^^^
  '''

test "setter keyword before static method", ->
  assertErrorFormat '''
    class A
      set @foo = ->
  ''', '''
    [stdin]:2:3: error: 'set' cannot be used as a keyword, or as a function call without parentheses
      set @foo = ->
      ^^^
  '''

test "#4248: Unicode code point escapes", ->
  assertErrorFormat '''
    "a
      #{b} \\u{G02}
     c"
  ''', '''
    [stdin]:2:8: error: invalid escape sequence \\u{G02}
      #{b} \\u{G02}
           ^\^^^^^^
  '''
  assertErrorFormat '''
    /a\\u{}b/
  ''', '''
    [stdin]:1:3: error: invalid escape sequence \\u{}
    /a\\u{}b/
      ^\^^^
  '''
  assertErrorFormat '''
    ///a \\u{01abc///
  ''', '''
    [stdin]:1:6: error: invalid escape sequence \\u{01abc
    ///a \\u{01abc///
         ^\^^^^^^^
  '''

  assertErrorFormat '''
    /\\u{123} \\u{110000}/
  ''', '''
    [stdin]:1:10: error: unicode code point escapes greater than \\u{10ffff} are not allowed
    /\\u{123} \\u{110000}/
      \       ^\^^^^^^^^^
  '''

  assertErrorFormat '''
    ///abc\\\\\\u{123456}///u
  ''', '''
    [stdin]:1:9: error: unicode code point escapes greater than \\u{10ffff} are not allowed
    ///abc\\\\\\u{123456}///u
           \ \^\^^^^^^^^^
  '''

  assertErrorFormat '''
    """
      \\u{123}
      a
        \\u{00110000}
      #{ 'b' }
    """
  ''', '''
    [stdin]:4:5: error: unicode code point escapes greater than \\u{10ffff} are not allowed
        \\u{00110000}
        ^\^^^^^^^^^^^
  '''

  assertErrorFormat '''
    '\\u{a}\\u{1111110000}'
  ''', '''
    [stdin]:1:7: error: unicode code point escapes greater than \\u{10ffff} are not allowed
    '\\u{a}\\u{1111110000}'
      \    ^\^^^^^^^^^^^^^
  '''

test "JSX error: non-matching tag names", ->
  assertErrorFormat '''
    <div><span></div></span>
  ''',
  '''
    [stdin]:1:7: error: expected corresponding JSX closing tag for span
    <div><span></div></span>
          ^^^^
  '''

test "JSX error: bare expressions not allowed", ->
  assertErrorFormat '''
    <div x=3 />
  ''',
  '''
    [stdin]:1:8: error: expected wrapped or quoted JSX attribute
    <div x=3 />
           ^
  '''

test "JSX error: unescaped opening tag angle bracket disallowed", ->
  assertErrorFormat '''
    <Person><<</Person>
  ''',
  '''
    [stdin]:1:9: error: unexpected <<
    <Person><<</Person>
            ^^
  '''

test "JSX error: ambiguous tag-like expression", ->
  assertErrorFormat '''
    x = a <b > c
  ''',
  '''
    [stdin]:1:10: error: missing </
    x = a <b > c
             ^
  '''

test 'JSX error: invalid attributes', ->
  assertErrorFormat '''
    <div a="b" {props} />
  ''', '''
    [stdin]:1:12: error: Unexpected token. Allowed JSX attributes are: id="val", src={source}, {props...} or attribute.
    <div a="b" {props} />
               ^^^^^^^
  '''
  assertErrorFormat '''
    <div a={b} {a:{b}} />
  ''', '''
    [stdin]:1:12: error: Unexpected token. Allowed JSX attributes are: id="val", src={source}, {props...} or attribute.
    <div a={b} {a:{b}} />
               ^^^^^^^
  '''
  assertErrorFormat '''
    <div {"#{a}"} />
  ''', '''
    [stdin]:1:6: error: Unexpected token. Allowed JSX attributes are: id="val", src={source}, {props...} or attribute.
    <div {"#{a}"} />
         ^^^^^^^^
  '''
  assertErrorFormat '''
    <div props... />
  ''', '''
    [stdin]:1:11: error: Unexpected token. Allowed JSX attributes are: id="val", src={source}, {props...} or attribute.
    <div props... />
              ^^^
  '''
  assertErrorFormat '''
    <div {a:"b", props..., c:d()} />
  ''', '''
    [stdin]:1:6: error: Unexpected token. Allowed JSX attributes are: id="val", src={source}, {props...} or attribute.
    <div {a:"b", props..., c:d()} />
         ^^^^^^^^^^^^^^^^^^^^^^^^
  '''
  assertErrorFormat '''
    <div {props..., a, b} />
  ''', '''
    [stdin]:1:6: error: Unexpected token. Allowed JSX attributes are: id="val", src={source}, {props...} or attribute.
    <div {props..., a, b} />
         ^^^^^^^^^^^^^^^^
  '''

test '#5034: JSX error: Adjacent JSX elements must be wrapped in an enclosing tag', ->
  assertErrorFormat '''
    render = -> (
      <Row>a</Row>
      <Row>b</Row>
    )
  ''', '''
    [stdin]:3:3: error: Adjacent JSX elements must be wrapped in an enclosing tag
      <Row>b</Row>
      ^^^^^^^^^^^^
  '''
  assertErrorFormat '''
    render = -> (
      a = "foo"
      <Row>a</Row>
      <Row>b</Row>
    )
  ''', '''
    [stdin]:4:3: error: Adjacent JSX elements must be wrapped in an enclosing tag
      <Row>b</Row>
      ^^^^^^^^^^^^
  '''
test 'Bound method called as callback before binding throws runtime error', ->
  class Base
    constructor: ->
      f = @derivedBound
      try
        f()
        ok no
      catch e
        eq e.message, 'Bound instance method accessed before binding'

  class Derived extends Base
    derivedBound: =>
      ok no
  d = new Derived

test "#3845/#3446: chain after function glyph (but not inline)", ->
  assertErrorFormat '''
    a -> .b
  ''',
  '''
    [stdin]:1:6: error: unexpected .
    a -> .b
         ^
  '''

test "#3906: error for unusual indentation", ->
  assertErrorFormat '''
    a
      c
     .d

    e(
     f)

    g
  ''', '''
    [stdin]:2:1: error: unexpected indentation
      c
    ^^
  '''

test "#4283: error message for implicit call", ->
  assertErrorFormat '''
    (a, b c) ->
  ''', '''
    [stdin]:1:5: error: unexpected implicit function call
    (a, b c) ->
        ^
  '''

test "#3199: error message for call indented non-object", ->
  assertErrorFormat '''
    fn = ->
    fn
      1
  ''', '''
    [stdin]:3:1: error: unexpected indentation
      1
    ^^
  '''

test "#3199: error message for call indented comprehension", ->
  assertErrorFormat '''
    fn = ->
    fn
      x for x in [1, 2, 3]
  ''', '''
    [stdin]:3:1: error: unexpected indentation
      x for x in [1, 2, 3]
    ^^
  '''

test "#3199: error message for return indented non-object", ->
  assertErrorFormat '''
    return
      1
  ''', '''
    [stdin]:2:3: error: unexpected number
      1
      ^
  '''

test "#3199: error message for return indented comprehension", ->
  assertErrorFormat '''
    return
      x for x in [1, 2, 3]
  ''', '''
    [stdin]:2:3: error: unexpected identifier
      x for x in [1, 2, 3]
      ^
  '''

test "#3199: error message for throw indented non-object", ->
  assertErrorFormat '''
    throw
      1
  ''', '''
    [stdin]:2:3: error: unexpected number
      1
      ^
  '''

test "#3199: error message for throw indented comprehension", ->
  assertErrorFormat '''
    throw
      x for x in [1, 2, 3]
  ''', '''
    [stdin]:2:3: error: unexpected identifier
      x for x in [1, 2, 3]
      ^
  '''

test "#3199: error message for yield indented non-object", ->
  assertErrorFormat '''
    ->
      yield
        1
  ''', '''
    [stdin]:3:5: error: unexpected number
        1
        ^
  '''

test "#3199: error message for yield indented comprehension", ->
  assertErrorFormat '''
    ->
      yield
        x for x in [1, 2, 3]
  ''', '''
    [stdin]:3:5: error: unexpected identifier
        x for x in [1, 2, 3]
        ^
  '''

test "#3199: error message for await indented non-object", ->
  assertErrorFormat '''
    ->
      await
        1
  ''', '''
    [stdin]:3:5: error: unexpected number
        1
        ^
  '''

test "#3199: error message for await indented comprehension", ->
  assertErrorFormat '''
    ->
      await
        x for x in [1, 2, 3]
  ''', '''
    [stdin]:3:5: error: unexpected identifier
        x for x in [1, 2, 3]
        ^
  '''

test "#3098: suppressed newline should be unsuppressed by semicolon", ->
  assertErrorFormat '''
    a = ; 5
  ''', '''
    [stdin]:1:5: error: unexpected ;
    a = ; 5
        ^
  '''

test "#4811: '///' inside a heregex comment does not close the heregex", ->
  assertErrorFormat '''
   /// .* # comment ///
  ''', '''
  [stdin]:1:1: error: missing ///
  /// .* # comment ///
  ^^^
  '''

test "#3933: prevent implicit calls when cotrol flow is missing `THEN`", ->
  assertErrorFormat '''
    for a in b do ->
  ''','''
    [stdin]:1:12: error: unexpected do
    for a in b do ->
               ^^
  '''

  assertErrorFormat '''
    for a in b ->
  ''','''
    [stdin]:1:12: error: unexpected ->
    for a in b ->
               ^^
  '''

  assertErrorFormat '''
    for a in b do =>
  ''','''
    [stdin]:1:12: error: unexpected do
    for a in b do =>
               ^^
  '''

  assertErrorFormat '''
    while a do ->
  ''','''
    [stdin]:1:9: error: unexpected do
    while a do ->
            ^^
  '''

  assertErrorFormat '''
    until a do =>
  ''','''
    [stdin]:1:9: error: unexpected do
    until a do =>
            ^^
  '''

  assertErrorFormat '''
    switch
      when a ->
  ''','''
    [stdin]:2:10: error: unexpected ->
      when a ->
             ^^
  '''

test "`new.target` outside of a function", ->
  assertErrorFormat '''
    new.target
  ''', '''
    [stdin]:1:1: error: new.target can only occur inside functions
    new.target
    ^^^^^^^^^^
  '''

test "`new.target` is only allowed meta property", ->
  assertErrorFormat '''
    -> new.something
  ''', '''
    [stdin]:1:4: error: the only valid meta property for new is new.target
    -> new.something
       ^^^^^^^^^^^^^
  '''

test "`import.meta` is only allowed meta property", ->
  assertErrorFormat '''
    foo = import.something
  ''', '''
    [stdin]:1:7: error: the only valid meta property for import is import.meta
    foo = import.something
          ^^^^^^^^^^^^^^^^
  '''

test "`new.target` cannot be assigned", ->
  assertErrorFormat '''
    ->
      new.target = b
  ''', '''
    [stdin]:2:14: error: unexpected =
      new.target = b
                 ^
  '''

test "#4834: dynamic import accepts either one or two arguments", ->
  assertErrorFormat '''
    import()
  ''', '''
    [stdin]:1:1: error: import() accepts either one or two arguments
    import()
    ^^^^^^^^
  '''

  assertErrorFormat '''
    import('x', {}, 3)
  ''', '''
    [stdin]:1:1: error: import() accepts either one or two arguments
    import('x', {}, 3)
    ^^^^^^^^^^^^^^^^^^
  '''

test "#4834: dynamic import requires explicit call parentheses", ->
  assertErrorFormat '''
    promise = import 'foo'
  ''', '''
    [stdin]:1:23: error: unexpected end of input
    promise = import 'foo'
                          ^
  '''

</script>
<script type="text/x-coffeescript" class="test" id="eval">
if vm = require? 'vm'

  test "CoffeeScript.eval runs in the global context by default", ->
    global.punctuation = '!'
    code = '''
    global.fhqwhgads = "global superpower#{global.punctuation}"
    '''
    result = CoffeeScript.eval code
    eq result, 'global superpower!'
    eq fhqwhgads, 'global superpower!'

  test "CoffeeScript.eval can run in, and modify, a Script context sandbox", ->
    createContext = vm.Script.createContext ? vm.createContext
    sandbox = createContext()
    sandbox.foo = 'bar'
    code = '''
    global.foo = 'not bar!'
    '''
    result = CoffeeScript.eval code, {sandbox}
    eq result, 'not bar!'
    eq sandbox.foo, 'not bar!'

  test "CoffeeScript.eval can run in, but cannot modify, an ordinary object sandbox", ->
    sandbox = {foo: 'bar'}
    code = '''
    global.foo = 'not bar!'
    '''
    result = CoffeeScript.eval code, {sandbox}
    eq result, 'not bar!'
    eq sandbox.foo, 'bar'

</script>
<script type="text/x-coffeescript" class="test" id="exception_handling">
# Exception Handling
# ------------------

# shared nonce
nonce = {}


# Throw

test "basic exception throwing", ->
  throws (-> throw 'error'), /^error$/


# Empty Try/Catch/Finally

test "try can exist alone", ->
  try

test "try/catch with empty try, empty catch", ->
  try
    # nothing
  catch err
    # nothing

test "single-line try/catch with empty try, empty catch", ->
  try catch err

test "try/finally with empty try, empty finally", ->
  try
    # nothing
  finally
    # nothing

test "single-line try/finally with empty try, empty finally", ->
  try finally

test "try/catch/finally with empty try, empty catch, empty finally", ->
  try
  catch err
  finally

test "single-line try/catch/finally with empty try, empty catch, empty finally", ->
  try catch err then finally


# Try/Catch/Finally as an Expression

test "return the result of try when no exception is thrown", ->
  result = try
    nonce
  catch err
    undefined
  finally
    undefined
  eq nonce, result

test "single-line result of try when no exception is thrown", ->
  result = try nonce catch err then undefined
  eq nonce, result

test "return the result of catch when an exception is thrown", ->
  fn = ->
    try
      throw ->
    catch err
      nonce
  doesNotThrow fn
  eq nonce, fn()

test "single-line result of catch when an exception is thrown", ->
  fn = ->
    try throw (->) catch err then nonce
  doesNotThrow fn
  eq nonce, fn()

test "optional catch", ->
  fn = ->
    try throw ->
    nonce
  doesNotThrow fn
  eq nonce, fn()


# Try/Catch/Finally Interaction With Other Constructs

test "try/catch with empty catch as last statement in a function body", ->
  fn = ->
    try nonce
    catch err
  eq nonce, fn()

test "#1595: try/catch with a reused variable name", ->
  # `catch` shouldn’t lead to broken scoping.
  do ->
    try
      inner = 5
    catch inner
      # nothing
  eq typeof inner, 'undefined'

test "#2580: try/catch with destructuring the exception object", ->
  result = try
    missing.object
  catch {message}
    message

  eq message, 'missing is not defined'

test "Try catch finally as implicit arguments", ->
  first = (x) -> x

  foo = no
  try
    first try iamwhoiam() finally foo = yes
  catch e
  eq foo, yes

  bar = no
  try
    first try iamwhoiam() catch e finally
    bar = yes
  catch e
  eq bar, yes

test "#2900: parameter-less catch clause", ->
  # `catch` should not require a parameter.
  try
    throw new Error 'failed'
  catch
    ok true

  try throw new Error 'failed' catch finally ok true

  ok try throw new Error 'failed' catch then true

test "#3709: throwing an if statement", ->
  # `throw if` should return a closure around the `if` block, so that the
  # output is valid JavaScript.
  try
    throw if no
        new Error 'drat!'
      else
        new Error 'no escape!'
  catch err
    eq err.message, 'no escape!'

  try
    throw if yes then new Error 'huh?' else null
  catch err
    eq err.message, 'huh?'

test "#3709: throwing a switch statement", ->
  i = 3
  try
    throw switch i
      when 2
        new Error 'not this one'
      when 3
        new Error 'oh no!'
  catch err
    eq err.message, 'oh no!'

test "#3709: throwing a for loop", ->
  # `throw for` should return a closure around the `for` block, so that the
  # output is valid JavaScript.
  try
    throw for i in [0..3]
      i * 2
  catch err
    arrayEq err, [0, 2, 4, 6]

test "#3709: throwing a while loop", ->
  i = 0
  try
    throw while i < 3
      i++
  catch err
    eq i, 3

test "#3789: throwing a throw", ->
  try
    throw throw throw new Error 'whoa!'
  catch err
    eq err.message, 'whoa!'

</script>
<script type="text/x-coffeescript" class="test" id="exponentiation">
# The `**` and `**=` operators are only supported in Node 7.5+, so the tests
# for these exponentiation operators are split out into their own file to be
# loaded only by supported runtimes.

test "exponentiation operator", ->
  eq 27, 3 ** 3

test "exponentiation operator has higher precedence than other maths operators", ->
  eq 55, 1 + 3 ** 3 * 2
  eq -4, -2 ** 2
  eq 0, (!2) ** 2

test "exponentiation operator is right associative", ->
  eq 2, 2 ** 1 ** 3

test "exponentiation operator compound assignment", ->
  a = 2
  a **= 3
  eq 8, a

</script>
<script type="text/x-coffeescript" class="test" id="formatting">
# Formatting
# ----------

# TODO: maybe this file should be split up into their respective sections:
#   operators -> operators
#   array literals -> array literals
#   string literals -> string literals
#   function invocations -> function invocations

doesNotThrowCompileError "a = then b"

test "multiple semicolon-separated statements in parentheticals", ->
  nonce = {}
  eq nonce, (1; 2; nonce)
  eq nonce, (-> return (1; 2; nonce))()

# * Line Continuation
#   * Property Accesss
#   * Operators
#   * Array Literals
#   * Function Invocations
#   * String Literals

# Property Access

test "chained accesses split on period/newline, backwards and forwards", ->
  str = 'abc'
  result = str.
    split('').
    reverse().
    reverse().
    reverse()
  arrayEq ['c','b','a'], result
  arrayEq ['c','b','a'], str.
    split('').
    reverse().
    reverse().
    reverse()
  result = str
    .split('')
    .reverse()
    .reverse()
    .reverse()
  arrayEq ['c','b','a'], result
  arrayEq ['c','b','a'],
    str
    .split('')
    .reverse()
    .reverse()
    .reverse()
  arrayEq ['c','b','a'],
    str.
    split('')
    .reverse().
    reverse()
    .reverse()

# Operators

test "newline suppression for operators", ->
  six =
    1 +
    2 +
    3
  eq 6, six

test "`?.` and `::` should continue lines", ->
  ok not (
    Date
    ::
    ?.foo
  )

  ok not (
    Date
    ?::
    ?.foo
  )
  #eq Object::toString, Date?.
  #prototype
  #::
  #?.foo

doesNotThrowCompileError """
  oh. yes
  oh?. true
  oh:: return
  """

doesNotThrowCompileError """
  a?[b..]
  a?[...b]
  a?[b..c]
  """

test "#1768: space between `::` and index is ignored", ->
  eq 'function', typeof String:: ['toString']

# Array Literals

test "indented array literals don't trigger whitespace rewriting", ->
  getArgs = -> arguments
  result = getArgs(
    [[[[[],
                  []],
                [[]]]],
      []])
  eq 1, result.length

# Function Invocations

doesNotThrowCompileError """
  obj = then fn 1,
    1: 1
    a:
      b: ->
        fn c,
          d: e
    f: 1
  """

# String Literals

test "indented heredoc", ->
  result = ((_) -> _)(
                """
                abc
                """)
  eq "abc", result

# Chaining - all open calls are closed by property access starting a new line
# * chaining after
#   * indented argument
#   * function block
#   * indented object
#
#   * single line arguments
#   * inline function literal
#   * inline object literal
#
# * chaining inside
#   * implicit object literal

test "chaining after outdent", ->
  id = (x) -> x

  # indented argument
  ff = id parseInt "ff",
    16
  .toString()
  eq '255', ff

  # function block
  str = 'abc'
  zero = parseInt str.replace /\w/, (letter) ->
    0
  .toString()
  eq '0', zero

  # indented object
  a = id id
    a: 1
  .a
  eq 1, a

test "#1495, method call chaining", ->
  str = 'abc'

  result = str.split ''
              .join ', '
  eq 'a, b, c', result

  result = str
  .split ''
  .join ', '
  eq 'a, b, c', result

  eq 'a, b, c', (str
    .split ''
    .join ', '
  )

  eq 'abc',
    'aaabbbccc'.replace /(\w)\1\1/g, '$1$1'
               .replace /([abc])\1/g, '$1'

  # Nested calls
  result = [1..3]
    .slice Math.max 0, 1
    .concat [3]
  arrayEq [2, 3, 3], result

  # Single line function arguments
  result = [1..6]
    .map (x) -> x * x
    .filter (x) -> x % 2 is 0
    .reverse()
  arrayEq [36, 16, 4], result

  # Single line implicit objects
  id = (x) -> x
  result = id a: 1
    .a
  eq 1, result

  # The parens are forced
  result = str.split(''.
    split ''
    .join ''
  ).join ', '
  eq 'a, b, c', result

test "chaining should not wrap spilling ternary", ->
  throwsCompileError """
    if 0 then 1 else g
      a: 42
    .h()
  """

test "chaining should wrap calls containing spilling ternary", ->
  f = (x) -> h: x
  id = (x) -> x
  result = f if true then 42 else id
      a: 2
  .h
  eq 42, result

test "chaining should work within spilling ternary", ->
  f = (x) -> h: x
  id = (x) -> x
  result = f if false then 1 else id
      a: 3
      .a
  eq 3, result.h

test "method call chaining inside objects", ->
  f = (x) -> c: 42
  result =
    a: f 1
    b: f a: 1
      .c
  eq 42, result.b

test "#4568: refine sameLine implicit object tagging", ->
  condition = yes
  fn = -> yes

  x =
    fn bar: {
      foo: 123
    } if not condition
  eq x, undefined

# Nested blocks caused by paren unwrapping
test "#1492: Nested blocks don't cause double semicolons", ->
  js = CoffeeScript.compile '(0;0)'
  eq -1, js.indexOf ';;'

test "#1195 Ignore trailing semicolons (before newlines or as the last char in a program)", ->
  preNewline = (numSemicolons) ->
    """
    nonce = {}; nonce2 = {}
    f = -> nonce#{Array(numSemicolons+1).join(';')}
    nonce2
    unless f() is nonce then throw new Error('; before linebreak should = newline')
    """
  CoffeeScript.run(preNewline(n), bare: true) for n in [1,2,3]

  lastChar = '-> lastChar;'
  doesNotThrowCompileError lastChar, bare: true

test "#1299: Disallow token misnesting", ->
  try
    CoffeeScript.compile '''
      [{
         ]}
    '''
    ok no
  catch e
    eq 'unmatched ]', e.message

test "#2981: Enforce initial indentation", ->
  try
    CoffeeScript.compile '  a\nb-'
    ok no
  catch e
    eq 'missing indentation', e.message

test "'single-line' expression containing multiple lines", ->
  doesNotThrowCompileError """
    (a, b) -> if a
      -a
    else if b
    then -b
    else null
  """

test "#1275: allow indentation before closing brackets", ->
  array = [
      1
      2
      3
    ]
  eq array, array
  do ->
  (
    a = 1
   )
  eq 1, a

test "don’t allow mixing of spaces and tabs for indentation", ->
  try
    CoffeeScript.compile '''
      new Layer
       x: 0
      	y: 1
    '''
    ok no
  catch e
    eq 'indentation mismatch', e.message

test "each code block that starts at indentation 0 can use a different style", ->
  doesNotThrowCompileError '''
      new Layer
       x: 0
       y: 1
      new Layer
      	x: 0
      	y: 1
    '''

test "tabs and spaces cannot be mixed for indentation", ->
  try
    CoffeeScript.compile '''
      new Layer
      	 x: 0
      	 y: 1
    '''
    ok no
  catch e
    eq 'mixed indentation', e.message

test "#4487: Handle unusual outdentation", ->
  a =
    switch 1
      when 2
          no
         when 3 then no
      when 1 then yes
  eq yes, a

  b = do ->
    if no
      if no
            1
       2
      3
  eq b, undefined

test "#3906: handle further indentation inside indented chain", ->
  eq 1, CoffeeScript.eval '''
    z = b: -> d: 2
    e = ->
    f = 3

    z
        .b ->
            c
        .d

    e(
        f
    )

    1
  '''

  eq 1, CoffeeScript.eval '''
    z = -> b: -> e: ->

    z()
        .b
            c: 'd'
        .e()

    f = [
        'g'
    ]

    1
  '''

  eq 1, CoffeeScript.eval '''
    z = -> c: -> c: ->

    z('b')
      .c 'a',
        {b: 'a'}
      .c()
    z(
      'b'
    )
    1
  '''

test "#3199: throw multiline implicit object", ->
  x = do ->
    if no then throw
      type: 'a'
      msg: 'b'
  eq undefined, x

  y = do ->
    if no then return
      type: 'a'
      msg: 'b'
  eq undefined, y

  y = do ->
    yield
      type: 'a'
      msg: 'b'

    if no then yield
      type: 'c'
      msg: 'd'

    1
  {value, done} = y.next()
  ok value.type is 'a' and done is no

  {value, done} = y.next()
  ok value is 1 and done is yes

test "#4576: multiple row function chaining", ->
  ->
    eq @a, 3
  .call a: 3

test "#4576: function chaining on separate rows", ->
  do ->
    Promise
    .resolve()
    .then ->
      yes
    .then ok

test "#3736: chaining after do IIFE", ->
  eq 3,
    do ->
      a: 3
    .a

  eq 3,
    do (b = (c) -> c) -> a: 3
    ?.a

  b = 3
  eq 3,
    do (
      b
      {d} = {}
    ) ->
      a: b
    .a

  # preserve existing chaining behavior for non-IIFE `do`
  b = c: -> 4
  eq 4,
    do b
    .c

test "#5168: allow indented property index", ->
  a = b: 3

  eq 3, a[
    if yes
      'b'
    else
      'c'
  ]

  d = [1, 2, 3]
  arrayEq [1, 2], d[
    ...2
  ]

  class A
    b: -> 3

  class B extends A
    c: ->
      super[
        'b'
      ]()

  eq 3, new B().c()

</script>
<script type="text/x-coffeescript" class="test" id="function_invocation">
# Function Invocation
# -------------------

# * Function Invocation
# * Splats in Function Invocations
# * Implicit Returns
# * Explicit Returns

# shared identity function
id = (_) -> if arguments.length is 1 then _ else [arguments...]

test "basic argument passing", ->

  a = {}
  b = {}
  c = {}
  eq 1, (id 1)
  eq 2, (id 1, 2)[1]
  eq a, (id a)
  eq c, (id a, b, c)[2]


test "passing arguments on separate lines", ->

  a = {}
  b = {}
  c = {}
  ok(id(
    a
    b
    c
  )[1] is b)
  eq(0, id(
    0
    10
  )[0])
  eq(a,id(
    a
  ))
  eq b,
  (id b)


test "optional parens can be used in a nested fashion", ->

  call = (func) -> func()
  add = (a,b) -> a + b
  result = call ->
    inner = call ->
      add 5, 5
  ok result is 10


test "hanging commas and semicolons in argument list", ->

  fn = () -> arguments.length
  eq 2, fn(0,1,)
  eq 3, fn 0, 1,
  2
  eq 2, fn(0, 1;)
  # TODO: this test fails (the string compiles), but should it?
  #throwsCompileError "fn(0,1,;)"
  throwsCompileError "fn(0,1,;;)"
  throwsCompileError "fn(0, 1;,)"
  throwsCompileError "fn(,0)"
  throwsCompileError "fn(;0)"


test "function invocation", ->

  func = ->
    return if true
  eq undefined, func()

  result = ("hello".slice) 3
  ok result is 'lo'


test "And even with strange things like this:", ->

  funcs  = [((x) -> x), ((x) -> x * x)]
  result = funcs[1] 5
  ok result is 25


test "More fun with optional parens.", ->

  fn = (arg) -> arg
  ok fn(fn {prop: 101}).prop is 101

  okFunc = (f) -> ok(f())
  okFunc -> true


test "chained function calls", ->
  nonce = {}
  identityWrap = (x) ->
    -> x
  eq nonce, identityWrap(identityWrap(nonce))()()
  eq nonce, (identityWrap identityWrap nonce)()()


test "Multi-blocks with optional parens.", ->

  fn = (arg) -> arg
  result = fn( ->
    fn ->
      "Wrapped"
  )
  ok result()() is 'Wrapped'


test "method calls", ->

  fnId = (fn) -> -> fn.apply this, arguments
  math = {
    add: (a, b) -> a + b
    anonymousAdd: (a, b) -> a + b
    fastAdd: fnId (a, b) -> a + b
  }
  ok math.add(5, 5) is 10
  ok math.anonymousAdd(10, 10) is 20
  ok math.fastAdd(20, 20) is 40


test "Ensure that functions can have a trailing comma in their argument list", ->

  mult = (x, mids..., y) ->
    x *= n for n in mids
    x *= y
  #ok mult(1, 2,) is 2
  #ok mult(1, 2, 3,) is 6
  ok mult(10, (i for i in [1..6])...) is 7200


test "`@` and `this` should both be able to invoke a method", ->
  nonce = {}
  fn          = (arg) -> eq nonce, arg
  fn.withAt   = -> @ nonce
  fn.withThis = -> this nonce
  fn.withAt()
  fn.withThis()


test "Trying an implicit object call with a trailing function.", ->

  a = null
  meth = (arg, obj, func) -> a = [obj.a, arg, func()].join ' '
  meth 'apple', b: 1, a: 13, ->
    'orange'
  ok a is '13 apple orange'


test "Ensure that empty functions don't return mistaken values.", ->

  obj =
    func: (@param, @rest...) ->
  ok obj.func(101, 102, 103, 104) is undefined
  ok obj.param is 101
  ok obj.rest.join(' ') is '102 103 104'


test "Passing multiple functions without paren-wrapping is legal, and should compile.", ->

  sum = (one, two) -> one() + two()
  result = sum ->
    7 + 9
  , ->
    1 + 3
  ok result is 20


test "Implicit call with a trailing if statement as a param.", ->

  func = -> arguments[1]
  result = func 'one', if false then 100 else 13
  ok result is 13


test "Test more function passing:", ->

  sum = (one, two) -> one() + two()

  result = sum( ->
    1 + 2
  , ->
    2 + 1
  )
  ok result is 6

  sum = (a, b) -> a + b
  result = sum(1
  , 2)
  ok result is 3


test "Chained blocks, with proper indentation levels:", ->

  counter =
    results: []
    tick: (func) ->
      @results.push func()
      this
  counter
    .tick ->
      3
    .tick ->
      2
    .tick ->
      1
  arrayEq [3,2,1], counter.results


test "This is a crazy one.", ->

  x = (obj, func) -> func obj
  ident = (x) -> x
  result = x {one: ident 1}, (obj) ->
    inner = ident(obj)
    ident inner
  ok result.one is 1


test "More paren compilation tests:", ->

  reverse = (obj) -> obj.reverse()
  ok reverse([1, 2].concat 3).join(' ') is '3 2 1'


test "Test for inline functions with parentheses and implicit calls.", ->

  combine = (func, num) -> func() * num
  result  = combine (-> 1 + 2), 3
  ok result is 9


test "Test for calls/parens/multiline-chains.", ->

  f = (x) -> x
  result = (f 1).toString()
    .length
  ok result is 1


test "Test implicit calls in functions in parens:", ->

  result = ((val) ->
    [].push val
    val
  )(10)
  ok result is 10


test "Ensure that chained calls with indented implicit object literals below are alright.", ->

  result = null
  obj =
    method: (val)  -> this
    second: (hash) -> result = hash.three
  obj
    .method(
      101
    ).second(
      one:
        two: 2
      three: 3
    )
  eq result, 3


test "Test newline-supressed call chains with nested functions.", ->

  obj  =
    call: -> this
  func = ->
    obj
      .call ->
        one two
      .call ->
        three four
    101
  eq func(), 101


test "Implicit objects with number arguments.", ->

  func = (x, y) -> y
  obj =
    prop: func "a", 1
  ok obj.prop is 1


test "Non-spaced unary and binary operators should cause a function call.", ->

  func = (val) -> val + 1
  ok (func +5) is 6
  ok (func -5) is -4


test "Prefix unary assignment operators are allowed in parenless calls.", ->

  func = (val) -> val + 1
  val = 5
  ok (func --val) is 5

test "#855: execution context for `func arr...` should be `null`", ->
  contextTest = -> eq @, if window? then window else global
  array = []
  contextTest array
  contextTest.apply null, array
  contextTest array...

test "#904: Destructuring function arguments with same-named variables in scope", ->
  a = b = nonce = {}
  fn = ([a,b]) -> {a:a,b:b}
  result = fn([c={},d={}])
  eq c, result.a
  eq d, result.b
  eq nonce, a
  eq nonce, b

test "Simple Destructuring function arguments with same-named variables in scope", ->
  x = 1
  f = ([x]) -> x
  eq f([2]), 2
  eq x, 1

test "#4843: Bad output when assigning to @prop in destructuring assignment with defaults", ->
  works = "maybe"
  drinks = "beer"
  class A
    constructor: ({@works = 'no', @drinks = 'wine'}) ->
  a = new A {works: 'yes', drinks: 'coffee'}
  eq a.works, 'yes'
  eq a.drinks, 'coffee'

test "caching base value", ->

  obj =
    index: 0
    0: {method: -> this is obj[0]}
  ok obj[obj.index++].method([]...)


test "passing splats to functions", ->
  arrayEq [0..4], id id [0..4]...
  fn = (a, b, c..., d) -> [a, b, c, d]
  range = [0..3]
  [first, second, others, last] = fn range..., 4, [5...8]...
  eq 0, first
  eq 1, second
  arrayEq [2..6], others
  eq 7, last

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  arrayEq [0..4], id id [0..4] ...
  fn = (a, b, c ..., d) -> [a, b, c, d]
  range = [0..3]
  [first, second, others, last] = fn range ..., 4, [5 ... 8] ...
  eq 0, first
  eq 1, second
  arrayEq [2..6], others
  eq 7, last

test "splat variables are local to the function", ->
  outer = "x"
  clobber = (avar, outer...) -> outer
  clobber "foo", "bar"
  eq "x", outer

test "Issue 4631: left and right spread dots with preceding space", ->
  a = []
  f = (a) -> a
  eq yes, (f ...a) is (f ... a) is (f a...) is (f a ...) is f(a...) is f(...a) is f(a ...) is f(... a)

test "Issue 894: Splatting against constructor-chained functions.", ->

  x = null
  class Foo
    bar: (y) -> x = y
  new Foo().bar([101]...)
  eq x, 101


test "Functions with splats being called with too few arguments.", ->

  pen = null
  method = (first, variable..., penultimate, ultimate) ->
    pen = penultimate
  method 1, 2, 3, 4, 5, 6, 7, 8, 9
  ok pen is 8
  method 1, 2, 3
  ok pen is 2
  method 1, 2
  ok pen is 2


test "splats with super() within classes.", ->

  class Parent
    meth: (args...) ->
      args
  class Child extends Parent
    meth: ->
      nums = [3, 2, 1]
      super nums...
  ok (new Child).meth().join(' ') is '3 2 1'

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  class Parent
    meth: (args ...) ->
      args
  class Child extends Parent
    meth: ->
      nums = [3, 2, 1]
      super nums ...
  ok (new Child).meth().join(' ') is '3 2 1'


test "#1011: passing a splat to a method of a number", ->
  eq '1011', 11.toString [2]...
  eq '1011', (31).toString [3]...
  eq '1011', 69.0.toString [4]...
  eq '1011', (131.0).toString [5]...

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  eq '1011', 11.toString [2] ...
  eq '1011', (31).toString [3] ...
  eq '1011', 69.0.toString [4] ...
  eq '1011', (131.0).toString [5] ...

test "splats and the `new` operator: functions that return `null` should construct their instance", ->
  args = []
  child = new (constructor = -> null) args...
  ok child instanceof constructor

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  child = new (constructor = -> null) args ...
  ok child instanceof constructor

test "splats and the `new` operator: functions that return functions should construct their return value", ->
  args = []
  fn = ->
  child = new (constructor = -> fn) args...
  ok child not instanceof constructor
  eq fn, child

test "implicit return", ->

  eq ok, new ->
    ok
    ### Should `return` implicitly   ###
    ### even with trailing comments. ###


test "implicit returns with multiple branches", ->
  nonce = {}
  fn = ->
    if false
      for a in b
        return c if d
    else
      nonce
  eq nonce, fn()


test "implicit returns with switches", ->
  nonce = {}
  fn = ->
    switch nonce
      when nonce then nonce
      else return undefined
  eq nonce, fn()


test "preserve context when generating closure wrappers for expression conversions", ->
  nonce = {}
  obj =
    property: nonce
    method: ->
      this.result = if false
        10
      else
        "a"
        "b"
        this.property
  eq nonce, obj.method()
  eq nonce, obj.property


test "don't wrap 'pure' statements in a closure", ->
  nonce = {}
  items = [0, 1, 2, 3, nonce, 4, 5]
  fn = (items) ->
    for item in items
      return item if item is nonce
  eq nonce, fn items


test "usage of `new` is careful about where the invocation parens end up", ->
  eq 'object', typeof new try Array
  eq 'object', typeof new do -> ->
  a = b: ->
  eq 'object', typeof new (do -> a).b


test "implicit call against control structures", ->
  result = null
  save   = (obj) -> result = obj

  save switch id false
    when true
      'true'
    when false
      'false'

  eq result, 'false'

  save if id false
    'false'
  else
    'true'

  eq result, 'true'

  save unless id false
    'true'
  else
    'false'

  eq result, 'true'

  save try
    doesnt exist
  catch error
    'caught'

  eq result, 'caught'

  save try doesnt(exist) catch error then 'caught2'

  eq result, 'caught2'


test "#1420: things like `(fn() ->)`; there are no words for this one", ->
  fn = -> (f) -> f()
  nonce = {}
  eq nonce, (fn() -> nonce)

test "#1416: don't omit one 'new' when compiling 'new new'", ->
  nonce = {}
  obj = new new -> -> {prop: nonce}
  eq obj.prop, nonce

test "#1416: don't omit one 'new' when compiling 'new new fn()()'", ->
  nonce = {}
  argNonceA = {}
  argNonceB = {}
  fn = (a) -> (b) -> {a, b, prop: nonce}
  obj = new new fn(argNonceA)(argNonceB)
  eq obj.prop, nonce
  eq obj.a, argNonceA
  eq obj.b, argNonceB

test "#1840: accessing the `prototype` after function invocation should compile", ->
  doesNotThrowCompileError 'fn()::prop'

  nonce = {}
  class Test then id: nonce

  dotAccess = -> Test::
  protoAccess = -> Test

  eq dotAccess().id, nonce
  eq protoAccess()::id, nonce

test "#960: improved 'do'", ->

  do (nonExistent = 'one') ->
    eq nonExistent, 'one'

  overridden = 1
  do (overridden = 2) ->
    eq overridden, 2

  two = 2
  do (one = 1, two, three = 3) ->
    eq one, 1
    eq two, 2
    eq three, 3

  ret = do func = (two) ->
    eq two, 2
    func
  eq ret, func

test "#2617: implicit call before unrelated implicit object", ->
  pass = ->
    true

  result = if pass 1
    one: 1
  eq result.one, 1

test "#2292, b: f (z),(x)", ->
  f = (x, y) -> y
  one = 1
  two = 2
  o = b: f (one),(two)
  eq o.b, 2

test "#2297, Different behaviors on interpreting literal", ->
  foo = (x, y) -> y
  bar =
    baz: foo 100, on

  eq bar.baz, on

  qux = (x) -> x
  quux = qux
    corge: foo 100, true

  eq quux.corge, on

  xyzzy =
    e: 1
    f: foo
      a: 1
      b: 2
    ,
      one: 1
      two: 2
      three: 3
    g:
      a: 1
      b: 2
      c: foo 2,
        one: 1
        two: 2
        three: 3
      d: 3
    four: 4
    h: foo one: 1, two: 2, three: three: three: 3,
      2

  eq xyzzy.f.two, 2
  eq xyzzy.g.c.three, 3
  eq xyzzy.four, 4
  eq xyzzy.h, 2

test "#2715, Chained implicit calls", ->
  first  = (x)    -> x
  second = (x, y) -> y

  foo = first first
    one: 1
  eq foo.one, 1

  bar = first second
    one: 1, 2
  eq bar, 2

  baz = first second
    one: 1,
    2
  eq baz, 2

test "Implicit calls and new", ->
  first = (x) -> x
  foo = (@x) ->
  bar = first new foo first 1
  eq bar.x, 1

  third = (x, y, z) -> z
  baz = first new foo new foo third
        one: 1
        two: 2
        1
        three: 3
        2
  eq baz.x.x.three, 3

test "Loose tokens inside of explicit call lists", ->
  first = (x) -> x
  second = (x, y) -> y
  one = 1

  foo = second( one
                2)
  eq foo, 2

  bar = first( first
               one: 1)
  eq bar.one, 1

test "Non-callable literals shouldn't compile", ->
  throwsCompileError '1(2)'
  throwsCompileError '1 2'
  throwsCompileError '/t/(2)'
  throwsCompileError '/t/ 2'
  throwsCompileError '///t///(2)'
  throwsCompileError '///t/// 2'
  throwsCompileError "''(2)"
  throwsCompileError "'' 2"
  throwsCompileError '""(2)'
  throwsCompileError '"" 2'
  throwsCompileError '""""""(2)'
  throwsCompileError '"""""" 2'
  throwsCompileError '{}(2)'
  throwsCompileError '{} 2'
  throwsCompileError '[](2)'
  throwsCompileError '[] 2'
  throwsCompileError '[2..9] 2'
  throwsCompileError '[2..9](2)'
  throwsCompileError '[1..10][2..9] 2'
  throwsCompileError '[1..10][2..9](2)'

test "implicit invocation with implicit object literal", ->
  f = (obj) -> eq 1, obj.a

  f
    a: 1
  obj =
    if f
      a: 2
    else
      a: 1
  eq 2, obj.a

  f
    "a": 1
  obj =
    if f
      "a": 2
    else
      "a": 1
  eq 2, obj.a

  # #3935: Implicit call when the first key of an implicit object has interpolation.
  a = 'a'
  f
    "#{a}": 1
  obj =
    if f
      "#{a}": 2
    else
      "#{a}": 1
  eq 2, obj.a

test "get and set can be used as function names when not ambiguous with `get`/`set` keywords", ->
  get = (val) -> val
  set = (val) -> val
  eq 2, get(2)
  eq 3, set(3)
  eq 'a', get('a')
  eq 'b', set('b')
  eq 4, get 4
  eq 5, set 5
  eq 'c', get 'c'
  eq 'd', set 'd'

  @get = get
  @set = set
  eq 6, @get 6
  eq 7, @set 7

  get = ({val}) -> val
  set = ({val}) -> val
  eq 8, get({val: 8})
  eq 9, set({val: 9})
  eq 'e', get({val: 'e'})
  eq 'f', set({val: 'f'})
  eq 10, get {val: 10}
  eq 11, set {val: 11}
  eq 'g', get {val: 'g'}
  eq 'h', set {val: 'h'}

test "get and set can be used as variable and property names", ->
  get = 2
  set = 3
  eq 2, get
  eq 3, set

  {get} = {get: 4}
  {set} = {set: 5}
  eq 4, get
  eq 5, set

test "get and set can be used as class method names", ->
  class A
    get: -> 2
    set: -> 3

  a = new A()
  eq 2, a.get()
  eq 3, a.set()

  class B
    @get = -> 4
    @set = -> 5

  eq 4, B.get()
  eq 5, B.set()

test "#4524: functions named get or set can be used without parentheses when attached to an object", ->
  obj =
    get: (x) -> x + 2
    set: (x) -> x + 3

  class A
    get: (x) -> x + 4
    set: (x) -> x + 5

  a = new A()

  class B
    get: (x) -> x.value + 6
    set: (x) -> x.value + 7

  b = new B()

  eq 12, obj.get 10
  eq 13, obj.set 10
  eq 12, obj?.get 10
  eq 13, obj?.set 10

  eq 14, a.get 10
  eq 15, a.set 10

  @ten = 10

  eq 12, obj.get @ten
  eq 13, obj.set @ten

  eq 14, a.get @ten
  eq 15, a.set @ten

  obj.obj = obj

  eq 12, obj.obj.get @ten
  eq 13, obj.obj.set @ten

  eq 16, b.get value: 10
  eq 17, b.set value: 10

  eq 16, b.get value: @ten
  eq 17, b.set value: @ten

test "#4836: functions named get or set can be used without parentheses when attached to this or @", ->
  @get = (x) -> x + 2
  @set = (x) -> x + 3
  @a = 4

  eq 12, this.get 10
  eq 13, this.set 10
  eq 12, this?.get 10
  eq 13, this?.set 10
  eq 6, this.get @a
  eq 7, this.set @a
  eq 6, this?.get @a
  eq 7, this?.set @a

  eq 12, @get 10
  eq 13, @set 10
  eq 12, @?.get 10
  eq 13, @?.set 10
  eq 6, @get @a
  eq 7, @set @a
  eq 6, @?.get @a
  eq 7, @?.set @a

test "#4852: functions named get or set can be used without parentheses when attached to this or @, with an argument of an implicit object", ->
  @get = ({ x }) -> x + 2
  @set = ({ x }) -> x + 3

  eq 12, @get x: 10
  eq 13, @set x: 10
  eq 12, @?.get x: 10
  eq 13, @?.set x: 10
  eq 12, this?.get x: 10
  eq 13, this?.set x: 10

test "#4473: variable scope in chained calls", ->
  obj =
    foo: -> this
    bar: (a) ->
      a()
      this

  obj.foo(a = 1).bar(-> a = 2)
  eq a, 2

  obj.bar(-> b = 2).foo(b = 1)
  eq b, 1

  obj.foo(c = 1).bar(-> c = 2).foo(c = 3)
  eq c, 3

  obj.foo([d, e] = [1, 2]).bar(-> d = 4)
  eq d, 4

  obj.foo({f} = {f: 1}).bar(-> f = 5)
  eq f, 5

test "#5052: implicit call of class with no body", ->
  doesNotThrowCompileError 'f class'
  doesNotThrowCompileError 'f class A'
  doesNotThrowCompileError 'f class A extends B'

  f = (args...) -> args
  a = 1

  [klass, shouldBeA] = f class A, a
  eq shouldBeA, a

  [shouldBeA] = f a, class A
  eq shouldBeA, a

  [obj, klass, shouldBeA] =
    f
      b: 1
      class A
      a
  eq shouldBeA, a

</script>
<script type="text/x-coffeescript" class="test" id="functions">
# Function Literals
# -----------------

# TODO: add indexing and method invocation tests: (->)[0], (->).call()

# * Function Definition
# * Bound Function Definition
# * Parameter List Features
#   * Splat Parameters
#   * Context (@) Parameters
#   * Parameter Destructuring
#   * Default Parameters

# Function Definition

x = 1
y = {}
y.x = -> 3
ok x is 1
ok typeof(y.x) is 'function'
ok y.x instanceof Function
ok y.x() is 3

# The empty function should not cause a syntax error.
->
() ->

# Multiple nested function declarations mixed with implicit calls should not
# cause a syntax error.
(one) -> (two) -> three four, (five) -> six seven, eight, (nine) ->

# with multiple single-line functions on the same line.
func = (x) -> (x) -> (x) -> x
ok func(1)(2)(3) is 3

# Make incorrect indentation safe.
func = ->
  obj = {
          key: 10
        }
  obj.key - 5
eq func(), 5

# Ensure that functions with the same name don't clash with helper functions.
del = -> 5
ok del() is 5


# Bound Function Definition

obj =
  bound: ->
    (=> this)()
  unbound: ->
    (-> this)()
  nested: ->
    (=>
      (=>
        (=> this)()
      )()
    )()
eq obj, obj.bound()
ok obj isnt obj.unbound()
eq obj, obj.nested()


test "even more fancy bound functions", ->
  obj =
    one: ->
      do =>
        return this.two()
    two: ->
      do =>
        do =>
          do =>
            return this.three
    three: 3

  eq obj.one(), 3


test "arguments in bound functions inherit from parent function", ->
  # The `arguments` object in an ES arrow function refers to the `arguments`
  # of the parent scope, just like `this`. In the CoffeeScript 1.x
  # implementation of `=>`, the `arguments` object referred to the arguments
  # of the arrow function; but per the ES2015 spec, `arguments` should refer
  # to the parent.
  arrayEq ((a...) -> a)([1, 2, 3]), ((a...) => a)([1, 2, 3])

  parent = (a, b, c) ->
    (bound = =>
      [arguments[0], arguments[1], arguments[2]]
    )()
  arrayEq [1, 2, 3], parent(1, 2, 3)


test "self-referencing functions", ->
  changeMe = ->
    changeMe = 2

  changeMe()
  eq changeMe, 2


# Parameter List Features

test "splats", ->
  arrayEq [0, 1, 2], (((splat...) -> splat) 0, 1, 2)
  arrayEq [2, 3], (((_, _1, splat...) -> splat) 0, 1, 2, 3)
  arrayEq [0, 1], (((splat..., _, _1) -> splat) 0, 1, 2, 3)
  arrayEq [2], (((_, _1, splat..., _2) -> splat) 0, 1, 2, 3)

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  arrayEq [0, 1, 2], (((splat ...) -> splat) 0, 1, 2)
  arrayEq [2, 3], (((_, _1, splat ...) -> splat) 0, 1, 2, 3)
  arrayEq [0, 1], (((splat ..., _, _1) -> splat) 0, 1, 2, 3)
  arrayEq [2], (((_, _1, splat ..., _2) -> splat) 0, 1, 2, 3)

test "destructured splatted parameters", ->
  arr = [0,1,2]
  splatArray = ([a...]) -> a
  splatArrayRest = ([a...],b...) -> arrayEq(a,b); b
  arrayEq splatArray(arr), arr
  arrayEq splatArrayRest(arr,0,1,2), arr

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  splatArray = ([a ...]) -> a
  splatArrayRest = ([a ...],b ...) -> arrayEq(a,b); b

test "#4884: object-destructured splatted parameters", ->
  f = ({length}...) -> length
  eq f(4, 5, 6), 3
  f = ({length: len}...) -> len
  eq f(4, 5, 6), 3
  f = ({length}..., last) -> [length, last]
  arrayEq f(4, 5, 6), [2, 6]
  f = ({length: len}..., last) -> [len, last]
  arrayEq f(4, 5, 6), [2, 6]

test "@-parameters: automatically assign an argument's value to a property of the context", ->
  nonce = {}

  ((@prop) ->).call context = {}, nonce
  eq nonce, context.prop

  # Allow splats alongside the special argument
  ((splat..., @prop) ->).apply context = {}, [0, 0, nonce]
  eq nonce, context.prop

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  ((splat ..., @prop) ->).apply context = {}, [0, 0, nonce]
  eq nonce, context.prop

  # Allow the argument itself to be a splat
  ((@prop...) ->).call context = {}, 0, nonce, 0
  eq nonce, context.prop[1]

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  ((@prop ...) ->).call context = {}, 0, nonce, 0
  eq nonce, context.prop[1]

  # The argument should not be able to be referenced normally
  code = '((@prop) -> prop).call {}'
  doesNotThrowCompileError code
  throws (-> CoffeeScript.run code), ReferenceError
  code = '((@prop) -> _at_prop).call {}'
  doesNotThrowCompileError code
  throws (-> CoffeeScript.run code), ReferenceError

test "@-parameters and splats with constructors", ->
  a = {}
  b = {}
  class Klass
    constructor: (@first, splat..., @last) ->

  obj = new Klass a, 0, 0, b
  eq a, obj.first
  eq b, obj.last

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  class Klass
    constructor: (@first, splat ..., @last) ->

  obj = new Klass a, 0, 0, b
  eq a, obj.first
  eq b, obj.last

test "destructuring in function definition", ->
  (([{a: [b], c}]...) ->
    eq 1, b
    eq 2, c
  ) {a: [1], c: 2}

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  (([{a: [b], c}] ...) ->
    eq 1, b
    eq 2, c
  ) {a: [1], c: 2}

  context = {}
  (([{a: [b, c = 2], @d, e = 4}]...) ->
    eq 1, b
    eq 2, c
    eq @d, 3
    eq context.d, 3
    eq e, 4
  ).call context, {a: [1], d: 3}

  (({a: aa = 1, b: bb = 2}) ->
    eq 5, aa
    eq 2, bb
  ) {a: 5}

  ajax = (url, {
    async = true,
    beforeSend = (->),
    cache = true,
    method = 'get',
    data = {}
  }) ->
    {url, async, beforeSend, cache, method, data}

  fn = ->
  deepEqual ajax('/home', beforeSend: fn, method: 'post'), {
    url: '/home', async: true, beforeSend: fn, cache: true, method: 'post', data: {}
  }

test "#4005: `([a = {}]..., b) ->` weirdness", ->
  fn = ([a = {}]..., b) -> [a, b]
  deepEqual fn(5), [{}, 5]

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  fn = ([a = {}] ..., b) -> [a, b]
  deepEqual fn(5), [{}, 5]

test "default values", ->
  nonceA = {}
  nonceB = {}
  a = (_,_1,arg=nonceA) -> arg
  eq nonceA, a()
  eq nonceA, a(0)
  eq nonceB, a(0,0,nonceB)
  eq nonceA, a(0,0,undefined)
  eq null, a(0,0,null) # Per ES2015, `null` doesn’t trigger a parameter default value
  eq false , a(0,0,false)
  eq nonceB, a(undefined,undefined,nonceB,undefined)
  b = (_,arg=nonceA,_1,_2) -> arg
  eq nonceA, b()
  eq nonceA, b(0)
  eq nonceB, b(0,nonceB)
  eq nonceA, b(0,undefined)
  eq null, b(0,null)
  eq false , b(0,false)
  eq nonceB, b(undefined,nonceB,undefined)
  c = (arg=nonceA,_,_1) -> arg
  eq nonceA, c()
  eq      0, c(0)
  eq nonceB, c(nonceB)
  eq nonceA, c(undefined)
  eq null, c(null)
  eq false , c(false)
  eq nonceB, c(nonceB,undefined,undefined)

test "default values with @-parameters", ->
  a = {}
  b = {}
  obj = f: (q = a, @p = b) -> q
  eq a, obj.f()
  eq b, obj.p

test "default values with splatted arguments", ->
  withSplats = (a = 2, b..., c = 3, d = 5) -> a * (b.length + 1) * c * d
  eq 30, withSplats()
  eq 15, withSplats(1)
  eq  5, withSplats(1,1)
  eq  1, withSplats(1,1,1)
  eq  2, withSplats(1,1,1,1)

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  withSplats = (a = 2, b ..., c = 3, d = 5) -> a * (b.length + 1) * c * d
  eq 30, withSplats()
  eq 15, withSplats(1)
  eq  5, withSplats(1,1)
  eq  1, withSplats(1,1,1)
  eq  2, withSplats(1,1,1,1)

test "#156: parameter lists with expansion", ->
  expandArguments = (first, ..., lastButOne, last) ->
    eq 1, first
    eq 4, lastButOne
    last
  eq 5, expandArguments 1, 2, 3, 4, 5

  throwsCompileError "(..., a, b...) ->", null, null, "prohibit expansion and a splat"
  throwsCompileError "(...) ->",          null, null, "prohibit lone expansion"

test "#156: parameter lists with expansion in array destructuring", ->
  expandArray = (..., [..., last]) ->
    last
  eq 3, expandArray 1, 2, 3, [1, 2, 3]

test "#3502: variable definitions and expansion", ->
  a = b = 0
  f = (a, ..., b) -> [a, b]
  arrayEq [1, 5], f 1, 2, 3, 4, 5
  eq 0, a
  eq 0, b

test "variable definitions and splat", ->
  a = b = 0
  f = (a, middle..., b) -> [a, middle, b]
  arrayEq [1, [2, 3, 4], 5], f 1, 2, 3, 4, 5
  eq 0, a
  eq 0, b

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  f = (a, middle ..., b) -> [a, middle, b]
  arrayEq [1, [2, 3, 4], 5], f 1, 2, 3, 4, 5
  eq 0, a
  eq 0, b

test "default values with function calls", ->
  doesNotThrowCompileError "(x = f()) ->"

test "arguments vs parameters", ->
  doesNotThrowCompileError "f(x) ->"
  f = (g) -> g()
  eq 5, f (x) -> 5

test "reserved keyword as parameters", ->
  f = (_case, @case) -> [_case, @case]
  [a, b] = f(1, 2)
  eq 1, a
  eq 2, b

  f = (@case, _case...) -> [@case, _case...]
  [a, b, c] = f(1, 2, 3)
  eq 1, a
  eq 2, b
  eq 3, c

test "reserved keyword at-splat", ->
  f = (@case...) -> @case
  [a, b] = f(1, 2)
  eq 1, a
  eq 2, b

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  f = (@case ...) -> @case
  [a, b] = f(1, 2)
  eq 1, a
  eq 2, b

test "#1574: Destructuring and a parameter named _arg", ->
  f = ({a, b}, _arg, _arg1) -> [a, b, _arg, _arg1]
  arrayEq [1, 2, 3, 4], f a: 1, b: 2, 3, 4

test "#1844: bound functions in nested comprehensions causing empty var statements", ->
  a = ((=>) for a in [0] for b in [0])
  eq 1, a.length

test "#1859: inline function bodies shouldn't modify prior postfix ifs", ->
  list = [1, 2, 3]
  ok true if list.some (x) -> x is 2

test "#2258: allow whitespace-style parameter lists in function definitions", ->
  func = (
    a, b, c
  ) -> c
  eq func(1, 2, 3), 3

  func = (
    a
    b
    c
  ) -> b
  eq func(1, 2, 3), 2

test "#2621: fancy destructuring in parameter lists", ->
  func = ({ prop1: { key1 }, prop2: { key2, key3: [a, b, c] } }) ->
    eq(key2, 'key2')
    eq(a, 'a')

  func({prop1: {key1: 'key1'}, prop2: {key2: 'key2', key3: ['a', 'b', 'c']}})

test "#1435 Indented property access", ->
  rec = -> rec: rec

  eq 1, do ->
    rec()
      .rec ->
        rec()
          .rec ->
            rec.rec()
          .rec()
    1

test "#1038 Optimize trailing return statements", ->
  compile = (code) -> CoffeeScript.compile(code, bare: yes).trim().replace(/\s+/g, " ")

  eq "(function() {});",                 compile("->")
  eq "(function() {});",                 compile("-> return")
  eq "(function() { return void 0; });", compile("-> undefined")
  eq "(function() { return void 0; });", compile("-> return undefined")
  eq "(function() { foo(); });",         compile("""
                                                 ->
                                                   foo()
                                                   return
                                                 """)

test "#4406 Destructured parameter default evaluation order with incrementing variable", ->
  i = 0
  f = ({ a = ++i }, b = ++i) -> [a, b]
  arrayEq f({}), [1, 2]

test "#4406 Destructured parameter default evaluation order with generator function", ->
  current = 0
  next    = -> ++current
  foo = ({ a = next() }, b = next()) -> [ a, b ]
  arrayEq foo({}), [1, 2]

test "Destructured parameter with default value, that itself has a default value", ->
  # Adapted from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
  draw = ({size = 'big', coords = {x: 0, y: 0}, radius = 25} = {}) -> "#{size}-#{coords.x}-#{coords.y}-#{radius}"
  output = draw
    coords:
      x: 18
      y: 30
    radius: 30
  eq output, 'big-18-30-30'

test "#4566: destructuring with nested default values", ->
  f = ({a: {b = 1}}) ->
    b
  eq 2, f a: b: 2

test "#1043: comma after function glyph", ->
  x = (a=->, b=2) ->
    a()
  eq x(), undefined

  f = (a) -> a()
  g = f ->, 2
  eq g, undefined
  h = f(=>, 2)
  eq h, undefined

test "#3845/#3446: chain after function glyph", ->
  angular = module: -> controller: -> controller: ->

  eq undefined,
    angular.module 'foo'
    .controller 'EmailLoginCtrl', ->
    .controller 'EmailSignupCtrl', ->

  beforeEach = (f) -> f()
  getPromise = -> then: -> catch: ->

  eq undefined,
    beforeEach ->
      getPromise()
      .then (@result) =>
      .catch (@error) =>

  doThing = -> then: -> catch: (f) -> f()
  handleError = -> 3
  eq 3,
    doThing()
    .then (@result) =>
    .catch handleError

test "#4413: expressions in function parameters that create generated variables have those variables declared correctly", ->
  'use strict'
  # We’re in strict mode because we want an error to be thrown if the generated
  # variable (`ref`) is assigned before being declared.
  foo = -> null
  bar = -> 33
  f = (a = foo() ? bar()) -> a
  g = (a = foo() ? bar()) -> a + 1
  eq f(), 33
  eq g(), 34

test "#4657: destructured array param declarations", ->
  a = 1
  b = 2
  f = ([a..., b]) ->
  f [3, 4, 5]
  eq a, 1
  eq b, 2

test "#4657: destructured array parameters", ->
  f = ([a..., b]) -> {a, b}
  result = f [1, 2, 3, 4]
  arrayEq result.a, [1, 2, 3]
  eq result.b, 4

test "#5128: default parameters of function in binary operation", ->
  foo = yes or (a, b = {}) -> null
  eq foo, yes

test "#5121: array end bracket after function glyph", ->
  a = [->]
  eq a.length, 1

  b = [c: ->]
  eq b.length, 1

</script>
<script type="text/x-coffeescript" class="test" id="generators">
# Generators
# -----------------
#
# * Generator Definition

test "most basic generator support", ->
  ok -> yield

test "empty generator", ->
  x = do -> yield return

  y = x.next()
  ok y.value is undefined and y.done is true

test "generator iteration", ->
  x = do ->
    yield 0
    yield
    yield 2
    3

  y = x.next()
  ok y.value is 0 and y.done is false

  y = x.next()
  ok y.value is undefined and y.done is false

  y = x.next()
  ok y.value is 2 and y.done is false

  y = x.next()
  ok y.value is 3 and y.done is true

test "last line yields are returned", ->
  x = do ->
    yield 3
  y = x.next()
  ok y.value is 3 and y.done is false

  y = x.next 42
  ok y.value is 42 and y.done is true

test "yield return can be used anywhere in the function body", ->
  x = do ->
    if 2 is yield 1
      yield return 42
    throw new Error "this code shouldn't be reachable"

  y = x.next()
  ok y.value is 1 and y.done is false

  y = x.next 2
  ok y.value is 42 and y.done is true

test "`yield from` support", ->
  x = do ->
    yield from do ->
      yield i for i in [3..4]

  y = x.next()
  ok y.value is 3 and y.done is false

  y = x.next 1
  ok y.value is 4 and y.done is false

  y = x.next 2
  arrayEq y.value, [1, 2]
  ok y.done is true

test "error if `yield from` occurs outside of a function", ->
  throwsCompileError 'yield from 1'

test "`yield from` at the end of a function errors", ->
  throwsCompileError 'x = -> x = 1; yield from'

test "yield in if statements", ->
  x = do -> if 1 is yield 2 then 3 else 4

  y = x.next()
  ok y.value is 2 and y.done is false

  y = x.next 1
  ok y.value is 3 and y.done is true

test "yielding if statements", ->
  x = do -> yield if true then 3 else 4

  y = x.next()
  ok y.value is 3 and y.done is false

  y = x.next 42
  ok y.value is 42 and y.done is true

test "yield in for loop expressions", ->
  x = do ->
    y = for i in [1..3]
      yield i * 2

  z = x.next()
  ok z.value is 2 and z.done is false

  z = x.next 10
  ok z.value is 4 and z.done is false

  z = x.next 20
  ok z.value is 6 and z.done is false

  z = x.next 30
  arrayEq z.value, [10, 20, 30]
  ok z.done is true

test "yield in switch expressions", ->
  x = do ->
    y = switch yield 1
      when 2 then yield 1337
      else 1336

  z = x.next()
  ok z.value is 1 and z.done is false

  z = x.next 2
  ok z.value is 1337 and z.done is false

  z = x.next 3
  ok z.value is 3 and z.done is true

test "yielding switch expressions", ->
  x = do ->
    yield switch 1337
      when 1337 then 1338
      else 1336

  y = x.next()
  ok y.value is 1338 and y.done is false

  y = x.next 42
  ok y.value is 42 and y.done is true

test "yield in try expressions", ->
  x = do ->
    try yield 1 catch

  y = x.next()
  ok y.value is 1 and y.done is false

  y = x.next 42
  ok y.value is 42 and y.done is true

test "yielding try expressions", ->
  x = do ->
    yield try 1

  y = x.next()
  ok y.value is 1 and y.done is false

  y = x.next 42
  ok y.value is 42 and y.done is true

test "`yield` can be thrown", ->
  x = do ->
    throw yield null
  x.next()
  throws -> x.next new Error "boom"

test "`throw` can be yielded", ->
  x = do ->
    yield throw new Error "boom"
  throws -> x.next()

test "symbolic operators has precedence over the `yield`", ->
  symbolic   = '+ - * / << >> & | || && ^ // or and'.split ' '
  compound   = ("#{op}=" for op in symbolic)
  relations  = '< > == != <= >= is isnt'.split ' '

  operators  = [symbolic..., '=', compound..., relations...]

  collect = (gen) -> ref.value until (ref = gen.next()).done

  values = [0, 1, 2, 3]
  for op in operators
    expression = "i #{op} 2"

    yielded = CoffeeScript.eval "(arr) ->  yield #{expression} for i in arr"
    mapped  = CoffeeScript.eval "(arr) ->       (#{expression} for i in arr)"

    arrayEq mapped(values), collect yielded values

test "yield handles 'this' correctly", ->
  x = ->
    yield switch
      when true then yield => this
    array = for item in [1]
      yield => this
    yield array
    yield if true then yield => this
    yield try throw yield => this
    throw yield => this

  y = x.call [1, 2, 3]

  z = y.next()
  arrayEq z.value(), [1, 2, 3]
  ok z.done is false

  z = y.next 123
  ok z.value is 123 and z.done is false

  z = y.next()
  arrayEq z.value(), [1, 2, 3]
  ok z.done is false

  z = y.next 42
  arrayEq z.value, [42]
  ok z.done is false

  z = y.next()
  arrayEq z.value(), [1, 2, 3]
  ok z.done is false

  z = y.next 456
  ok z.value is 456 and z.done is false

  z = y.next()
  arrayEq z.value(), [1, 2, 3]
  ok z.done is false

  z = y.next new Error "ignore me"
  ok z.value is undefined and z.done is false

  z = y.next()
  arrayEq z.value(), [1, 2, 3]
  ok z.done is false

  throws -> y.next new Error "boom"

test "for-from loops over generators", ->
  array1 = [50, 30, 70, 20]
  gen = -> yield from array1

  array2 = []
  array3 = []
  array4 = []

  iterator = gen()
  for x from iterator
    array2.push(x)
    break if x is 30

  for x from iterator
    array3.push(x)

  for x from iterator
    array4.push(x)

  arrayEq array2, [50, 30]
  # Different JS engines have different opinions on the value of array3:
  # https://github.com/jashkenas/coffeescript/pull/4306#issuecomment-257066877
  # As a temporary measure, either result is accepted.
  ok array3.length is 0 or array3.join(',') is '70,20'
  arrayEq array4, []

test "for-from comprehensions over generators", ->
  gen = ->
    yield from [30, 41, 51, 60]

  iterator = gen()
  array1 = (x for x from iterator when x %% 2 is 1)
  array2 = (x for x from iterator)

  ok array1.join(' ') is '41 51'
  ok array2.length is 0

test "from as an iterable variable name in a for loop declaration", ->
  from = [1, 2, 3]
  out = []
  for i from from
    out.push i
  arrayEq from, out

test "from as an iterator variable name in a for loop declaration", ->
  a = [1, 2, 3]
  b = []
  for from from a
    b.push from
  arrayEq a, b

test "from as a destructured object variable name in a for loop declaration", ->
  a = [
      from: 1
      to: 2
    ,
      from: 3
      to: 4
  ]
  b = []
  for {from, to} in a
    b.push from
  arrayEq b, [1, 3]

  c = []
  for {to, from} in a
    c.push from
  arrayEq c, [1, 3]

test "from as a destructured, aliased object variable name in a for loop declaration", ->
  a = [
      b: 1
      c: 2
    ,
      b: 3
      c: 4
  ]
  out = []

  for {b: from} in a
    out.push from
  arrayEq out, [1, 3]

test "from as a destructured array variable name in a for loop declaration", ->
  a = [
    [1, 2]
    [3, 4]
  ]
  b = []
  for [from, to] from a
    b.push from
  arrayEq b, [1, 3]

test "generator methods in classes", ->
  class Base
    @static: ->
      yield 1
    method: ->
      yield 2

  arrayEq [1], Array.from Base.static()
  arrayEq [2], Array.from new Base().method()

  class Child extends Base
    @static: -> super()
    method: -> super()

  arrayEq [1], Array.from Child.static()
  arrayEq [2], Array.from new Child().method()

</script>
<script type="text/x-coffeescript" class="test" id="helpers">
# Helpers
# -------

# pull the helpers from `CoffeeScript.helpers` into local variables
{starts, ends, repeat, compact, count, merge, extend, flatten, del, baseFileName} = CoffeeScript.helpers


# `starts`

test "the `starts` helper tests if a string starts with another string", ->
  ok     starts('01234', '012')
  ok not starts('01234', '123')

test "the `starts` helper can take an optional offset", ->
  ok     starts('01234', '34', 3)
  ok not starts('01234', '01', 1)


# `ends`

test "the `ends` helper tests if a string ends with another string", ->
  ok     ends('01234', '234')
  ok not ends('01234', '012')

test "the `ends` helper can take an optional offset", ->
  ok     ends('01234', '012', 2)
  ok not ends('01234', '234', 6)


# `repeat`

test "the `repeat` helper concatenates a given number of times", ->
  eq 'asdasdasd', repeat('asd', 3)

test "`repeat`ing a string 0 times always returns the empty string", ->
  eq '', repeat('whatever', 0)


# `compact`

test "the `compact` helper removes falsey values from an array, preserves truthy ones", ->
  allValues = [1, 0, false, obj={}, [], '', ' ', -1, null, undefined, true]
  truthyValues = [1, obj, [], ' ', -1, true]
  arrayEq truthyValues, compact(allValues)


# `count`

test "the `count` helper counts the number of occurrences of a string in another string", ->
  eq 1/0, count('abc', '')
  eq 0, count('abc', 'z')
  eq 1, count('abc', 'a')
  eq 1, count('abc', 'b')
  eq 2, count('abcdc', 'c')
  eq 2, count('abcdabcd','abc')


# `merge`

test "the `merge` helper makes a new object with all properties of the objects given as its arguments", ->
  ary = [0, 1, 2, 3, 4]
  obj = {}
  merged = merge obj, ary
  ok merged isnt obj
  ok merged isnt ary
  for own key, val of ary
    eq val, merged[key]


# `extend`

test "the `extend` helper performs a shallow copy", ->
  ary = [0, 1, 2, 3]
  obj = {}
  # should return the object being extended
  eq obj, extend(obj, ary)
  # should copy the other object's properties as well (obviously)
  eq 2, obj[2]


# `flatten`

test "the `flatten` helper flattens an array", ->
  success = yes
  (success and= typeof n is 'number') for n in flatten [0, [[[1]], 2], 3, [4]]
  ok success


# `del`

test "the `del` helper deletes a property from an object and returns the deleted value", ->
  obj = [0, 1, 2]
  eq 1, del(obj, 1)
  ok 1 not of obj


# `baseFileName`

test "the `baseFileName` helper returns the file name to write to", ->
  ext = '.js'
  sourceToCompiled =
    '.coffee': ext
    'a.coffee': 'a' + ext
    'b.coffee': 'b' + ext
    'coffee.coffee': 'coffee' + ext

    '.litcoffee': ext
    'a.litcoffee': 'a' + ext
    'b.litcoffee': 'b' + ext
    'coffee.litcoffee': 'coffee' + ext

    '.lit': ext
    'a.lit': 'a' + ext
    'b.lit': 'b' + ext
    'coffee.lit': 'coffee' + ext

    '.coffee.md': ext
    'a.coffee.md': 'a' + ext
    'b.coffee.md': 'b' + ext
    'coffee.coffee.md': 'coffee' + ext

  for sourceFileName, expectedFileName of sourceToCompiled
    name = baseFileName sourceFileName, yes
    filename = name + ext
    eq filename, expectedFileName

</script>
<script type="text/x-coffeescript" class="test" id="import_assertions">
# This file is running in CommonJS (in Node) or as a classic Script (in the browser tests) so it can use import() within an async function, but not at the top level; and we can’t use static import.
test "dynamic import assertion", ->
  try
    { default: secret } = await import('data:application/json,{"ofLife":42}', { assert: { type: 'json' } })
    eq secret.ofLife, 42
  catch exception
    # This parses on Node 16.14.x but throws an error because JSON modules aren’t unflagged there yet; remove this try/catch once the unflagging of `--experimental-json-modules` is backported (see https://github.com/nodejs/node/pull/41736#issuecomment-1086738670)
    unless exception.message is 'Invalid module "data:application/json,{"ofLife":42}" has an unsupported MIME type "application/json"'
      throw exception

test "assert keyword", ->
  assert = 1

  try
    { default: assert } = await import('data:application/json,{"thatIAm":42}', { assert: { type: 'json' } })
    eq assert.thatIAm, 42
  catch exception
    # This parses on Node 16.14.x but throws an error because JSON modules aren’t unflagged there yet; remove this try/catch once the unflagging of `--experimental-json-modules` is backported (see https://github.com/nodejs/node/pull/41736#issuecomment-1086738670)
    unless exception.message is 'Invalid module "data:application/json,{"thatIAm":42}" has an unsupported MIME type "application/json"'
      throw exception

  eqJS """
    import assert from 'regression-test'
  """, """
    import assert from 'regression-test';
  """

test "static import assertion", ->
  eqJS """
    import 'data:application/json,{"foo":3}' assert { type: 'json' }
  """, """
    import 'data:application/json,{"foo":3}' assert {
      type: 'json'
    };
  """

  eqJS """
    import secret from 'data:application/json,{"ofLife":42}' assert { type: 'json' }
  """, """
    import secret from 'data:application/json,{"ofLife":42}' assert {
      type: 'json'
    };
  """

  eqJS """
    import * as secret from 'data:application/json,{"ofLife":42}' assert { type: 'json' }
  """, """
    import * as secret from 'data:application/json,{"ofLife":42}' assert {
      type: 'json'
    };
  """

  # The only file types for which import assertions are currently supported are JSON (Node and browsers) and CSS (browsers), neither of which support named exports; however there’s nothing in the JavaScript grammar preventing a future supported file type from providing named exports.
  eqJS """
    import { foo } from './file.unknown' assert { type: 'unknown' }
  """, """
    import {
      foo
    } from './file.unknown' assert {
        type: 'unknown'
      };
  """

  eqJS """
    import file, { foo } from './file.unknown' assert { type: 'unknown' }
  """, """
    import file, {
      foo
    } from './file.unknown' assert {
        type: 'unknown'
      };
  """

  eqJS """
    import foo from 'bar' assert {}
  """, """
    import foo from 'bar' assert {};
  """

test "static export with assertion", ->
  eqJS """
    export * from 'data:application/json,{"foo":3}' assert { type: 'json' }
  """, """
    export * from 'data:application/json,{"foo":3}' assert {
      type: 'json'
    };
  """

  eqJS """
    export { profile } from './user.json' assert { type: 'json' }
  """, """
    export {
      profile
    } from './user.json' assert {
        type: 'json'
      };
  """

</script>
<script type="text/x-coffeescript" class="test" id="importing">
# Importing
# ---------

unless window? or testingBrowser?
  test "coffeescript modules can be imported and executed", ->

    magicKey = __filename
    magicValue = 0xFFFF

    if global[magicKey]?
      if exports?
        local = magicValue
        exports.method = -> local
    else
      global[magicKey] = {}
      if require?.extensions?
        ok require(__filename).method() is magicValue
      delete global[magicKey]

  test "javascript modules can be imported", ->
    magicVal = 1
    for module in 'import.js import2 .import2 import.extension.js import.unknownextension .coffee .coffee.md'.split ' '
      ok require("./importing/#{module}").value?() is magicVal, module

  test "coffeescript modules can be imported", ->
    magicVal = 2
    for module in '.import.coffee import.coffee import.extension.coffee'.split ' '
      ok require("./importing/#{module}").value?() is magicVal, module

  test "literate coffeescript modules can be imported", ->
    magicVal = 3
    # Leading space intentional to check for index.coffee.md
    for module in ' .import.coffee.md import.coffee.md import.litcoffee import.extension.coffee.md'.split ' '
      ok require("./importing/#{module}").value?() is magicVal, module

</script>
<script type="text/x-coffeescript" class="test" id="interpolation">
# Interpolation
# -------------

# * String Interpolation
# * Regular Expression Interpolation

# String Interpolation

# TODO: refactor string interpolation tests

eq 'multiline nested "interpolations" work', """multiline #{
  "nested #{
    ok true
    "\"interpolations\""
  }"
} work"""

# Issue #923: Tricky interpolation.
eq "#{ "{" }", "{"
eq "#{ '#{}}' } }", '#{}} }'
eq "#{"'#{ ({a: "b#{1}"}['a']) }'"}", "'b1'"

# Issue #1150: String interpolation regression
eq "#{'"/'}",                '"/'
eq "#{"/'"}",                "/'"
eq "#{/'"/}",                '/\'"/'
eq "#{"'/" + '/"' + /"'/}",  '\'//"/"\'/'
eq "#{"'/"}#{'/"'}#{/"'/}",  '\'//"/"\'/'
eq "#{6 / 2}",               '3'
eq "#{6 / 2}#{6 / 2}",       '33' # parsed as division
eq "#{6 + /2}#{6/ + 2}",     '6/2}#{6/2' # parsed as a regex
eq "#{6/2}
    #{6/2}",                 '3 3' # newline cannot be part of a regex, so it's division
eq "#{/// "'/'"/" ///}",     '/"\'\\/\'"\\/"/' # heregex, stuffed with spicy characters
eq "#{/\\'/}",               "/\\\\'/"

# Issue #2321: Regex/division conflict in interpolation
eq "#{4/2}/", '2/'
curWidth = 4
eq "<i style='left:#{ curWidth/2 }%;'></i>",   "<i style='left:2%;'></i>"
throwsCompileError '''
   "<i style='left:#{ curWidth /2 }%;'></i>"'''
#                 valid regex--^^^^^^^^^^^ ^--unclosed string
eq "<i style='left:#{ curWidth/2 }%;'></i>",   "<i style='left:2%;'></i>"
eq "<i style='left:#{ curWidth/ 2 }%;'></i>",  "<i style='left:2%;'></i>"
eq "<i style='left:#{ curWidth / 2 }%;'></i>", "<i style='left:2%;'></i>"

hello = 'Hello'
world = 'World'
ok '#{hello} #{world}!' is '#{hello} #{world}!'
ok "#{hello} #{world}!" is 'Hello World!'
ok "[#{hello}#{world}]" is '[HelloWorld]'
ok "#{hello}##{world}" is 'Hello#World'
ok "Hello #{ 1 + 2 } World" is 'Hello 3 World'
ok "#{hello} #{ 1 + 2 } #{world}" is "Hello 3 World"
ok 1 + "#{2}px" is '12px'
ok isNaN "a#{2}" * 2
ok "#{2}" is '2'
ok "#{2}#{2}" is '22'

[s, t, r, i, n, g] = ['s', 't', 'r', 'i', 'n', 'g']
ok "#{s}#{t}#{r}#{i}#{n}#{g}" is 'string'
ok "\#{s}\#{t}\#{r}\#{i}\#{n}\#{g}" is '#{s}#{t}#{r}#{i}#{n}#{g}'
ok "\#{string}" is '#{string}'

ok "\#{Escaping} first" is '#{Escaping} first'
ok "Escaping \#{in} middle" is 'Escaping #{in} middle'
ok "Escaping \#{last}" is 'Escaping #{last}'

ok "##" is '##'
ok "#{}" is ''
ok "#{}A#{} #{} #{}B#{}" is 'A  B'
ok "\\\#{}" is '\\#{}'

ok "I won ##{20} last night." is 'I won #20 last night.'
ok "I won ##{'#20'} last night." is 'I won ##20 last night.'

ok "#{hello + world}" is 'HelloWorld'
ok "#{hello + ' ' + world + '!'}" is 'Hello World!'

list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ok "values: #{list.join(', ')}, length: #{list.length}." is 'values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, length: 10.'
ok "values: #{list.join ' '}" is 'values: 0 1 2 3 4 5 6 7 8 9'

obj = {
  name: 'Joe'
  hi: -> "Hello #{@name}."
  cya: -> "Hello #{@name}.".replace('Hello','Goodbye')
}
ok obj.hi() is "Hello Joe."
ok obj.cya() is "Goodbye Joe."

ok "With #{"quotes"}" is 'With quotes'
ok 'With #{"quotes"}' is 'With #{"quotes"}'

ok "Where is #{obj["name"] + '?'}" is 'Where is Joe?'

ok "Where is #{"the nested #{obj["name"]}"}?" is 'Where is the nested Joe?'
ok "Hello #{world ? "#{hello}"}" is 'Hello World'

ok "Hello #{"#{"#{obj["name"]}" + '!'}"}" is 'Hello Joe!'

a = """
    Hello #{ "Joe" }
    """
ok a is "Hello Joe"

a = 1
b = 2
c = 3
ok "#{a}#{b}#{c}" is '123'

result = null
stash = (str) -> result = str
stash "a #{ ('aa').replace /a/g, 'b' } c"
ok result is 'a bb c'

foo = "hello"
ok "#{foo.replace("\"", "")}" is 'hello'

val = 10
a = """
    basic heredoc #{val}
    on two lines
    """
b = '''
    basic heredoc #{val}
    on two lines
    '''
ok a is "basic heredoc 10\non two lines"
ok b is "basic heredoc \#{val}\non two lines"

eq 'multiline nested "interpolations" work', """multiline #{
  "nested #{(->
    ok yes
    "\"interpolations\""
  )()}"
} work"""

eq 'function(){}', "#{->}".replace /\s/g, ''
ok /^a[\s\S]+b$/.test "a#{=>}b"
ok /^a[\s\S]+b$/.test "a#{ (x) -> x %% 2 }b"

# Regular Expression Interpolation

# TODO: improve heregex interpolation tests

test "heregex interpolation", ->
  eq /\\#{}\\"/ + '', ///
   #{
     "#{ '\\' }" # normal comment
   }
   # regex comment
   \#{}
   \\ "
  /// + ''

</script>
<script type="text/x-coffeescript" class="test" id="invocation_argument_parsing">
return unless require?

path = require 'path'
{ execFileSync, spawnSync } = require 'child_process'

# Get the folder containing the compiled `coffee` executable and make it the
# PATH so that `#!/usr/bin/env coffee` resolves to our locally built file.
coffeeBinFolder = path.dirname require.resolve '../bin/coffee'
# For some reason, Windows requires `coffee` to be executed as `node coffee`.
coffeeCommand = if isWindows() then 'node coffee' else 'coffee'
spawnOptions =
  cwd: coffeeBinFolder
  encoding: 'utf8'
  env: Object.assign {}, process.env,
    PATH: coffeeBinFolder + (if isWindows() then ';' else ':') + process.env.PATH
  shell: isWindows()

shebangScript = require.resolve './importing/shebang.coffee'
initialSpaceScript = require.resolve './importing/shebang_initial_space.coffee'
extraArgsScript = require.resolve './importing/shebang_extra_args.coffee'
initialSpaceExtraArgsScript = require.resolve './importing/shebang_initial_space_extra_args.coffee'

test "parse arguments for shebang scripts correctly (on *nix platforms)", ->
  return if isWindows()

  stdout = execFileSync shebangScript, ['-abck'], spawnOptions
  expectedArgs = ['coffee', shebangScript, '-abck']
  realArgs = JSON.parse stdout
  arrayEq expectedArgs, realArgs

  stdout = execFileSync initialSpaceScript, ['-abck'], spawnOptions
  expectedArgs = ['coffee', initialSpaceScript, '-abck']
  realArgs = JSON.parse stdout
  arrayEq expectedArgs, realArgs

test "warn and remove -- if it is the second positional argument", ->
  result = spawnSync coffeeCommand, [shebangScript, '--'], spawnOptions
  stderr = result.stderr.toString()
  arrayEq JSON.parse(result.stdout), ['coffee', shebangScript]
  ok stderr.match /^coffee was invoked with '--'/m
  posArgs = stderr.match(/^The positional arguments were: (.*)$/m)[1]
  arrayEq JSON.parse(posArgs), [shebangScript, '--']
  ok result.status is 0

  result = spawnSync coffeeCommand, ['-b', shebangScript, '--'], spawnOptions
  stderr = result.stderr.toString()
  arrayEq JSON.parse(result.stdout), ['coffee', shebangScript]
  ok stderr.match /^coffee was invoked with '--'/m
  posArgs = stderr.match(/^The positional arguments were: (.*)$/m)[1]
  arrayEq JSON.parse(posArgs), [shebangScript, '--']
  ok result.status is 0

  result = spawnSync(
    coffeeCommand, ['-b', shebangScript, '--', 'ANOTHER'], spawnOptions)
  stderr = result.stderr.toString()
  arrayEq JSON.parse(result.stdout), ['coffee', shebangScript, 'ANOTHER']
  ok stderr.match /^coffee was invoked with '--'/m
  posArgs = stderr.match(/^The positional arguments were: (.*)$/m)[1]
  arrayEq JSON.parse(posArgs), [shebangScript, '--', 'ANOTHER']
  ok result.status is 0

  result = spawnSync(
    coffeeCommand, ['--', initialSpaceScript, 'arg'], spawnOptions)
  expectedArgs = ['coffee', initialSpaceScript, 'arg']
  realArgs = JSON.parse result.stdout
  arrayEq expectedArgs, realArgs
  ok result.stderr.toString() is ''
  ok result.status is 0

test "warn about non-portable shebang lines", ->
  result = spawnSync coffeeCommand, [extraArgsScript, 'arg'], spawnOptions
  stderr = result.stderr.toString()
  arrayEq JSON.parse(result.stdout), ['coffee', extraArgsScript, 'arg']
  ok stderr.match /^The script to be run begins with a shebang line with more than one/m
  [_, firstLine, file] = stderr.match(/^The shebang line was: '([^']+)' in file '([^']+)'/m)
  ok (firstLine is '#!/usr/bin/env coffee --')
  ok (file is extraArgsScript)
  args = stderr.match(/^The arguments were: (.*)$/m)[1]
  arrayEq JSON.parse(args), ['coffee', '--']
  ok result.status is 0

  result = spawnSync coffeeCommand, [initialSpaceScript, 'arg'], spawnOptions
  stderr = result.stderr.toString()
  ok stderr is ''
  arrayEq JSON.parse(result.stdout), ['coffee', initialSpaceScript, 'arg']
  ok result.status is 0

  result = spawnSync(
    coffeeCommand, [initialSpaceExtraArgsScript, 'arg'], spawnOptions)
  stderr = result.stderr.toString()
  arrayEq JSON.parse(result.stdout), ['coffee', initialSpaceExtraArgsScript, 'arg']
  ok stderr.match /^The script to be run begins with a shebang line with more than one/m
  [_, firstLine, file] = stderr.match(/^The shebang line was: '([^']+)' in file '([^']+)'/m)
  ok (firstLine is '#! /usr/bin/env coffee extra')
  ok (file is initialSpaceExtraArgsScript)
  args = stderr.match(/^The arguments were: (.*)$/m)[1]
  arrayEq JSON.parse(args), ['coffee', 'extra']
  ok result.status is 0

test "both warnings will be shown at once", ->
  result = spawnSync(
    coffeeCommand, [initialSpaceExtraArgsScript, '--', 'arg'], spawnOptions)
  stderr = result.stderr.toString()
  arrayEq JSON.parse(result.stdout), ['coffee', initialSpaceExtraArgsScript, 'arg']
  ok stderr.match /^The script to be run begins with a shebang line with more than one/m
  [_, firstLine, file] = stderr.match(/^The shebang line was: '([^']+)' in file '([^']+)'/m)
  ok (firstLine is '#! /usr/bin/env coffee extra')
  ok (file is initialSpaceExtraArgsScript)
  args = stderr.match(/^The arguments were: (.*)$/m)[1]
  arrayEq JSON.parse(args), ['coffee', 'extra']
  ok stderr.match /^coffee was invoked with '--'/m
  posArgs = stderr.match(/^The positional arguments were: (.*)$/m)[1]
  arrayEq JSON.parse(posArgs), [initialSpaceExtraArgsScript, '--', 'arg']
  ok result.status is 0

</script>
<script type="text/x-coffeescript" class="test" id="javascript_literals">
# JavaScript Literals
# -------------------

test "inline JavaScript is evaluated", ->
  eq '\\`', `
    // Inline JS
    "\\\\\`"
  `

test "escaped backticks are output correctly", ->
  `var a = \`2 + 2 = ${4}\``
  eq a, '2 + 2 = 4'

test "backslashes before a newline don’t break JavaScript blocks", ->
  `var a = \`To be, or not\\
  to be.\``
  eq a, '''
  To be, or not\\
    to be.'''

test "block inline JavaScript is evaluated", ->
  ```
  var a = 1;
  var b = 2;
  ```
  c = 3
  ```var d = 4;```
  eq a + b + c + d, 10

test "block inline JavaScript containing backticks", ->
  ```
  // This is a comment with `backticks`
  var a = 42;
  var b = `foo ${'bar'}`;
  var c = 3;
  var d = 'foo`bar`';
  ```
  eq a + c, 45
  eq b, 'foo bar'
  eq d, 'foo`bar`'

test "block JavaScript can end with an escaped backtick character", ->
  ```var a = \`hello\````
  ```
  var b = \`world${'!'}\````
  eq a, 'hello'
  eq b, 'world!'

test "JavaScript block only escapes backslashes followed by backticks", ->
  eq `'\\\n'`, '\\\n'

test "escaped JavaScript blocks speed round", ->
  # The following has escaped backslashes because they’re required in strings, but the intent is this:
  # `hello`                                       → hello;
  # `\`hello\``                                   → `hello`;
  # `\`Escaping backticks in JS: \\\`hello\\\`\`` → `Escaping backticks in JS: \`hello\``;
  # `Single backslash: \ `                        → Single backslash: \ ;
  # `Double backslash: \\ `                       → Double backslash: \\ ;
  # `Single backslash at EOS: \\`                 → Single backslash at EOS: \;
  # `Double backslash at EOS: \\\\`               → Double backslash at EOS: \\;
  for [input, output] in [
    ['`hello`',                                               'hello;']
    ['`\\`hello\\``',                                         '`hello`;']
    ['`\\`Escaping backticks in JS: \\\\\\`hello\\\\\\`\\``', '`Escaping backticks in JS: \\`hello\\``;']
    ['`Single backslash: \\ `',                               'Single backslash: \\ ;']
    ['`Double backslash: \\\\ `',                             'Double backslash: \\\\ ;']
    ['`Single backslash at EOS: \\\\`',                       'Single backslash at EOS: \\;']
    ['`Double backslash at EOS: \\\\\\\\`',                   'Double backslash at EOS: \\\\;']
  ]
    eqJS input, output

</script>
<script type="text/x-coffeescript" class="test" id="jsx">
# We usually do not check the actual JS output from the compiler, but since
# JSX is not natively supported by Node, we do it in this case.

test 'self closing', ->
  eqJS '''
    <div />
  ''', '''
    <div />;
  '''

test 'self closing formatting', ->
  eqJS '''
    <div/>
  ''', '''
    <div />;
  '''

test 'self closing multiline', ->
  eqJS '''
    <div
    />
  ''', '''
    <div />;
  '''

test 'reserved-word self closing', ->
  eqJS '''
    <static />
  ''', '''
    <static />;
  '''

test 'regex attribute', ->
  eqJS '''
    <div x={/>asds/} />
  ''', '''
    <div x={/>asds/} />;
  '''

test 'string attribute', ->
  eqJS '''
    <div x="a" />
  ''', '''
    <div x="a" />;
  '''

test 'simple attribute', ->
  eqJS '''
    <div x={42} />
  ''', '''
    <div x={42} />;
  '''

test 'assignment attribute', ->
  eqJS '''
    <div x={y = 42} />
  ''', '''
    var y;

    <div x={y = 42} />;
  '''

test 'object attribute', ->
  eqJS '''
    <div x={{y: 42}} />
  ''', '''
    <div x={{
      y: 42
    }} />;
  '''

test 'attribute without value', ->
  eqJS '''
    <div checked x="hello" />
  ''', '''
    <div checked x="hello" />;
  '''

test 'reserved-word attribute without value', ->
  eqJS '''
    <div static x="hello" />
  ''', '''
    <div static x="hello" />;
  '''

test 'reserved-word attribute with value', ->
  eqJS '''
    <div static="yes" x="hello" />
  ''', '''
    <div static="yes" x="hello" />;
  '''

test 'attribute with namespace', ->
  eqJS '''
    <image xlink:href="data:image/png" />
  ''', '''
    <image xlink:href="data:image/png" />;
  '''

test 'attribute with double namespace disallowed', ->
  throwsCompileError '<image xlink:href:tag="data:image/png" />'

test 'attribute with member expression disallowed', ->
  throwsCompileError '<image xlink.href="data:image/png" />'

test 'paired', ->
  eqJS '''
    <div></div>
  ''', '''
    <div></div>;
  '''

test 'simple content', ->
  eqJS '''
    <div>Hello world</div>
  ''', '''
    <div>Hello world</div>;
  '''

test 'content interpolation', ->
  eqJS '''
    <div>Hello {42}</div>
  ''', '''
    <div>Hello {42}</div>;
  '''

test 'nested tag', ->
  eqJS '''
    <div><span /></div>
  ''', '''
    <div><span /></div>;
  '''

test 'tag inside interpolation formatting', ->
  eqJS '''
    <div>Hello {<span />}</div>
  ''', '''
    <div>Hello <span /></div>;
  '''

test 'tag inside interpolation, tags are callable', ->
  eqJS '''
    <div>Hello {<span /> x}</div>
  ''', '''
    <div>Hello {<span />(x)}</div>;
  '''

test 'tags inside interpolation, tags trigger implicit calls', ->
  eqJS '''
    <div>Hello {f <span />}</div>
  ''', '''
    <div>Hello {f(<span />)}</div>;
  '''

test 'regex in interpolation', ->
  eqJS '''
    <div x={/>asds/}><div />{/>asdsad</}</div>
  ''', '''
    <div x={/>asds/}><div />{/>asdsad</}</div>;
  '''

test 'interpolation in string attribute value', ->
  eqJS '''
    <div x="Hello #{world}" />
  ''', '''
    <div x={`Hello ${world}`} />;
  '''

# Unlike in `coffee-react-transform`.
test 'bare numbers not allowed', ->
  throwsCompileError '<div x=3 />'

test 'bare expressions not allowed', ->
  throwsCompileError '<div x=y />'

test 'bare complex expressions not allowed', ->
  throwsCompileError '<div x=f(3) />'

test 'unescaped opening tag angle bracket disallowed', ->
  throwsCompileError '<Person><<</Person>'

test 'space around equal sign', ->
  eqJS '''
    <div popular = "yes" />
  ''', '''
    <div popular="yes" />;
  '''

# The following tests were adopted from James Friend’s
# [https://github.com/jsdf/coffee-react-transform](https://github.com/jsdf/coffee-react-transform).

test 'ambiguous tag-like expression', ->
  throwsCompileError 'x = a <b > c'

test 'ambiguous tag', ->
  eqJS '''
    a <b > c </b>
  ''', '''
    a(<b> c </b>);
  '''

test 'escaped CoffeeScript attribute', ->
  eqJS '''
    <Person name={if test() then 'yes' else 'no'} />
  ''', '''
    <Person name={test() ? 'yes' : 'no'} />;
  '''

test 'escaped CoffeeScript attribute over multiple lines', ->
  eqJS '''
    <Person name={
      if test()
        'yes'
      else
        'no'
    } />
  ''', '''
    <Person name={test() ? 'yes' : 'no'} />;
  '''

test 'multiple line escaped CoffeeScript with nested JSX', ->
  eqJS '''
    <Person name={
      if test()
        'yes'
      else
        'no'
    }>
    {

      for n in a
        <div> a
          asf
          <li xy={"as"}>{ n+1 }<a /> <a /> </li>
        </div>
    }

    </Person>
  ''', '''
    var n;

    <Person name={test() ? 'yes' : 'no'}>
    {(function() {
      var i, len, results;
      results = [];
      for (i = 0, len = a.length; i < len; i++) {
        n = a[i];
        results.push(<div> a
          asf
          <li xy={"as"}>{n + 1}<a /> <a /> </li>
        </div>);
      }
      return results;
    })()}

    </Person>;
  '''

test 'nested JSX within an attribute, with object attr value', ->
  eqJS '''
    <Company>
      <Person name={<NameComponent attr3={ {'a': {}, b: '{'} } />} />
    </Company>
  ''', '''
    <Company>
      <Person name={<NameComponent attr3={{
      'a': {},
      b: '{'
    }} />} />
    </Company>;
  '''

test 'complex nesting', ->
  eqJS '''
    <div code={someFunc({a:{b:{}, C:'}{}{'}})} />
  ''', '''
    <div code={someFunc({
      a: {
        b: {},
        C: '}{}{'
      }
    })} />;
  '''

test 'multiline tag with nested JSX within an attribute', ->
  eqJS '''
    <Person
      name={
        name = formatName(user.name)
        <NameComponent name={name.toUppercase()} />
      }
    >
      blah blah blah
    </Person>
  ''', '''
    var name;

    <Person name={name = formatName(user.name), <NameComponent name={name.toUppercase()} />}>
      blah blah blah
    </Person>;
  '''

test 'escaped CoffeeScript with nested object literals', ->
  eqJS '''
    <Person>
      blah blah blah {
        {'a' : {}, 'asd': 'asd'}
      }
    </Person>
  ''', '''
    <Person>
      blah blah blah {{
      'a': {},
      'asd': 'asd'
    }}
    </Person>;
  '''

test 'multiline tag attributes with escaped CoffeeScript', ->
  eqJS '''
    <Person name={if isActive() then 'active' else 'inactive'}
    someattr='on new line' />
  ''', '''
    <Person name={isActive() ? 'active' : 'inactive'} someattr='on new line' />;
  '''

test 'lots of attributes', ->
  eqJS '''
    <Person eyes={2} friends={getFriends()} popular = "yes"
    active={ if isActive() then 'active' else 'inactive' } data-attr='works' checked check={me_out}
    />
  ''', '''
    <Person eyes={2} friends={getFriends()} popular="yes" active={isActive() ? 'active' : 'inactive'} data-attr='works' checked check={me_out} />;
  '''

# TODO: fix partially indented JSX
# test 'multiline elements', ->
#   eqJS '''
#     <div something={
#       do ->
#         test = /432/gm # this is a regex
#         6 /432/gm # this is division
#     }
#     >
#     <div>
#     <div>
#     <div>
#       <article name={ new Date() } number={203}
#        range={getRange()}
#       >
#       </article>
#     </div>
#     </div>
#     </div>
#     </div>
#   ''', '''
#     bla
#   '''

test 'complex regex', ->
  eqJS '''
    <Person />
    /\\/\\/<Person \\/>\\>\\//
  ''', '''
    <Person />;

    /\\/\\/<Person \\/>\\>\\//;
  '''

test 'heregex', ->
  eqJS '''
    test = /432/gm # this is a regex
    6 /432/gm # this is division
    <Tag>
    {test = /<Tag>/} this is a regex containing something which looks like a tag
    </Tag>
    <Person />
    REGEX = /// ^
      (/ (?! [\s=] )   # comment comment <comment>comment</comment>
      [^ [ / \n \\ ]*  # comment comment
      (?:
        <Tag />
        (?: \\[\s\S]   # comment comment
          | \[         # comment comment
               [^ \] \n \\ ]*
               (?: \\[\s\S] [^ \] \n \\ ]* )*
               <Tag>tag</Tag>
             ]
        ) [^ [ / \n \\ ]*
      )*
      /) ([imgy]{0,4}) (?!\w)
    ///
    <Person />
  ''', '''
    var REGEX, test;

    test = /432/gm; // this is a regex

    6 / 432 / gm; // this is division

    <Tag>
    {test = /<Tag>/} this is a regex containing something which looks like a tag
    </Tag>;

    <Person />;

    REGEX = /^(\\/(?![s=])[^[\\/ ]*(?:<Tag\\/>(?:\\[sS]|[[^] ]*(?:\\[sS][^] ]*)*<Tag>tag<\\/Tag>])[^[\\/ ]*)*\\/)([imgy]{0,4})(?!w)/; // comment comment <comment>comment</comment>
    // comment comment
    // comment comment
    // comment comment

    <Person />;
  '''

test 'comment within JSX is not treated as comment', ->
  eqJS '''
    <Person>
    # i am not a comment
    </Person>
  ''', '''
    <Person>
    # i am not a comment
    </Person>;
  '''

test 'comment at start of JSX escape', ->
  eqJS '''
    <Person>
    {# i am a comment
      "i am a string"
    }
    </Person>
  ''', '''
    <Person>
    {// i am a comment
    "i am a string"}
    </Person>;
  '''

test 'comment at end of JSX escape', ->
  eqJS '''
    <Person>
    {"i am a string"
    # i am a comment
    }
    </Person>
  ''', '''
    <Person>
    {"i am a string"
    // i am a comment
    }
    </Person>;
  '''

test 'JSX comment cannot be used inside interpolation', ->
  throwsCompileError '''
    <Person>
    {# i am a comment}
    </Person>
  '''

test 'comment syntax cannot be used inline', ->
  throwsCompileError '''
    <Person>{#comment inline}</Person>
  '''

test 'string within JSX is ignored', ->
  eqJS '''
    <Person> "i am not a string" 'nor am i' </Person>
  ''', '''
    <Person> "i am not a string" 'nor am i' </Person>;
  '''

test 'special chars within JSX are ignored', ->
  eqJS """
    <Person> a,/';][' a\''@$%^&˚¬∑˜˚∆å∂¬˚*()*&^%$>> '"''"'''\'\'m' i </Person>
  """, """
    <Person> a,/';][' a''@$%^&˚¬∑˜˚∆å∂¬˚*()*&^%$>> '"''"'''''m' i </Person>;
  """

test 'html entities (name, decimal, hex) within JSX', ->
  eqJS '''
    <Person>  &&&&euro;  &#8364; &#x20AC;;; </Person>
  ''', '''
    <Person>  &&&&euro;  &#8364; &#x20AC;;; </Person>;
  '''

test 'tag with {{}}', ->
  eqJS '''
    <Person name={{value: item, key, item}} />
  ''', '''
    <Person name={{
      value: item,
      key,
      item
    }} />;
  '''

test 'tag with member expression', ->
  eqJS '''
    <Something.Tag></Something.Tag>
  ''', '''
    <Something.Tag></Something.Tag>;
  '''

test 'tag with lowercase member expression', ->
  eqJS '''
    <something.tag></something.tag>
  ''', '''
    <something.tag></something.tag>;
  '''

test 'self closing tag with member expression', ->
  eqJS '''
    <Something.Tag />
  ''', '''
    <Something.Tag />;
  '''

test 'self closing tag with multiple member expressions', ->
  eqJS '''
    <Something.Tag.More />
  ''', '''
    <Something.Tag.More />;
  '''

test 'tag with namespace', ->
  eqJS '''
    <Something:Tag></Something:Tag>
  ''', '''
    <Something:Tag></Something:Tag>;
  '''

test 'tag with lowercase namespace', ->
  eqJS '''
    <something:tag></something:tag>
  ''', '''
    <something:tag></something:tag>;
  '''

test 'self closing tag with namespace', ->
  eqJS '''
    <Something:Tag />
  ''', '''
    <Something:Tag />;
  '''

test 'self closing tag with namespace and member expression disallowed', ->
  throwsCompileError '''
    <Namespace:Something.Tag />
  '''

test 'self closing tag with spread attribute', ->
  eqJS '''
    <Component a={b} {x...} b="c" />
  ''', '''
    <Component a={b} {...x} b="c" />;
  '''

test 'complex spread attribute', ->
  eqJS '''
    <Component {x...} a={b} {x...} b="c" {$my_xtraCoolVar123...} />
  ''', '''
    <Component {...x} a={b} {...x} b="c" {...$my_xtraCoolVar123} />;
  '''

test 'multiline spread attribute', ->
  eqJS '''
    <Component {
      x...} a={b} {x...} b="c" {z...}>
    </Component>
  ''', '''
    <Component {...x} a={b} {...x} b="c" {...z}>
    </Component>;
  '''

test 'multiline tag with spread attribute', ->
  eqJS '''
    <Component
      z="1"
      {x...}
      a={b}
      b="c"
    >
    </Component>
  ''', '''
    <Component z="1" {...x} a={b} b="c">
    </Component>;
  '''

test 'multiline tag with spread attribute first', ->
  eqJS '''
    <Component
      {x...}
      z="1"
      a={b}
      b="c"
    >
    </Component>
  ''', '''
    <Component {...x} z="1" a={b} b="c">
    </Component>;
  '''

test 'complex multiline spread attribute', ->
  eqJS '''
    <Component
      {y...
      } a={b} {x...} b="c" {z...}>
      <div code={someFunc({a:{b:{}, C:'}'}})} />
    </Component>
  ''', '''
    <Component {...y} a={b} {...x} b="c" {...z}>
      <div code={someFunc({
      a: {
        b: {},
        C: '}'
      }
    })} />
    </Component>;
  '''

test 'self closing spread attribute on single line', ->
  eqJS '''
    <Component a="b" c="d" {@props...} />
  ''', '''
    <Component a="b" c="d" {...this.props} />;
  '''

test 'self closing spread attribute on new line', ->
  eqJS '''
    <Component
      a="b"
      c="d"
      {@props...}
    />
  ''', '''
    <Component a="b" c="d" {...this.props} />;
  '''

test 'self closing spread attribute on same line', ->
  eqJS '''
    <Component
      a="b"
      c="d"
      {@props...} />
  ''', '''
    <Component a="b" c="d" {...this.props} />;
  '''

test 'self closing spread attribute on next line', ->
  eqJS '''
    <Component
      a="b"
      c="d"
      {@props...}

    />
  ''', '''
    <Component a="b" c="d" {...this.props} />;
  '''

test 'empty strings are not converted to true', ->
  eqJS '''
    <Component val="" />
  ''', '''
    <Component val="" />;
  '''

test 'CoffeeScript @ syntax in tag name', ->
  throwsCompileError '''
    <@Component>
      <Component />
    </@Component>
  '''

test 'hyphens in tag names', ->
  eqJS '''
    <paper-button className="button">{text}</paper-button>
  ''', '''
    <paper-button className="button">{text}</paper-button>;
  '''

test 'closing tags must be closed', ->
  throwsCompileError '''
    <a></a
  '''

# Tests for allowing less than operator without spaces when ther is no JSX

test 'unspaced less than without JSX: identifier', ->
  a = 3
  div = 5
  ok a<div

test 'unspaced less than without JSX: number', ->
  div = 5
  ok 3<div

test 'unspaced less than without JSX: paren', ->
  div = 5
  ok (3)<div

test 'unspaced less than without JSX: index', ->
  div = 5
  a = [3]
  ok a[0]<div

test 'tag inside JSX works following: identifier', ->
  eqJS '''
    <span>a<div /></span>
  ''', '''
    <span>a<div /></span>;
  '''

test 'tag inside JSX works following: number', ->
  eqJS '''
    <span>3<div /></span>
  ''', '''
    <span>3<div /></span>;
  '''

test 'tag inside JSX works following: paren', ->
  eqJS '''
    <span>(3)<div /></span>
  ''', '''
    <span>(3)<div /></span>;
  '''

test 'tag inside JSX works following: square bracket', ->
  eqJS '''
    <span>]<div /></span>
  ''', '''
    <span>]<div /></span>;
  '''

test 'unspaced less than inside JSX works but is not encouraged', ->
  eqJS '''
      a = 3
      div = 5
      html = <span>{a<div}</span>
    ''', '''
      var a, div, html;

      a = 3;

      div = 5;

      html = <span>{a < div}</span>;
    '''

test 'unspaced less than before JSX works but is not encouraged', ->
  eqJS '''
      div = 5
      res = 2<div
      html = <span />
    ''', '''
      var div, html, res;

      div = 5;

      res = 2 < div;

      html = <span />;
    '''

test 'unspaced less than after JSX works but is not encouraged', ->
  eqJS '''
      div = 5
      html = <span />
      res = 2<div
    ''', '''
      var div, html, res;

      div = 5;

      html = <span />;

      res = 2 < div;
    '''

test '#4686: comments inside interpolations that also contain JSX tags', ->
  eqJS '''
    <div>
      {
        # comment
        <div />
      }
    </div>
  ''', '''
    <div>
      {  // comment
    <div />}
    </div>;
  '''

test '#4686: comments inside interpolations that also contain JSX attributes', ->
  eqJS '''
    <div>
      <div anAttr={
        # comment
        "value"
      } />
    </div>
  ''', '''
    <div>
      {  // comment
    <div anAttr={"value"} />}
    </div>;
  '''

test '#5086: comments inside JSX tags but outside interpolations', ->
  eqJS '''
    <div>
      <div ###comment### attribute={value} />
    </div>
  ''', '''
    <div>
      <div /*comment*/attribute={value} />
    </div>;
  '''

test '#5086: comments inside JSX attributes but outside interpolations', ->
  eqJS '''
    <div>
      <div attribute={###attr comment### value} />
    </div>
  ''', '''
    <div>
      <div attribute={/*attr comment*/value} />
    </div>;
  '''

test '#5086: comments inside nested JSX tags and attributes but outside interpolations', ->
  eqJS '''
    <div>
      <div>
        <div>
          <div ###comment### attribute={###attr comment### value} />
        </div>
      </div>
    </div>
  ''', '''
    <div>
      <div>
        <div>
          <div /*comment*/attribute={/*attr comment*/value} />
        </div>
      </div>
    </div>;
  '''

# https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html
test 'JSX fragments: empty fragment', ->
  eqJS '''
    <></>
  ''', '''
    <></>;
  '''

test 'JSX fragments: fragment with text nodes', ->
  eqJS '''
    <>
      Some text.
      <h2>A heading</h2>
      More text.
      <h2>Another heading</h2>
      Even more text.
    </>
  ''', '''
    <>
      Some text.
      <h2>A heading</h2>
      More text.
      <h2>Another heading</h2>
      Even more text.
    </>;
  '''

test 'JSX fragments: fragment with component nodes', ->
  eqJS '''
    Component = (props) =>
      <Fragment>
        <OtherComponent />
        <OtherComponent />
      </Fragment>
  ''', '''
    var Component;

    Component = (props) => {
      return <Fragment>
        <OtherComponent />
        <OtherComponent />
      </Fragment>;
    };
  '''

test '#5055: JSX expression indentation bug', ->
  eqJS '''
    <div>
      {someCondition &&
        <span />
      }
    </div>
  ''', '''
    <div>
      {someCondition && <span />}
    </div>;
  '''

  eqJS '''
    <div>{someString +
         "abc"
      }
    </div>
  ''', '''
    <div>{someString + "abc"}
    </div>;
  '''

  eqJS '''
    <div>
      {a ?
      <span />
      }
    </div>
  ''', '''
    <div>
      {typeof a !== "undefined" && a !== null ? a : <span />}
    </div>;
  '''

# JSX is like XML, in that there needs to be a root element; but
# technically, adjacent top-level elements where only the last one
# is returned (as opposed to a fragment or root element) is permissible
# syntax. It’s almost certainly an error, but it’s valid, so need to leave it
# to linters to catch. https://github.com/jashkenas/coffeescript/pull/5049
test '“Adjacent” tags on separate lines should still compile', ->
  eqJS '''
    ->
      <a />
      <b />
  ''', '''
    (function() {
      <a />;
      return <b />;
    });
  '''

test '#5352: triple-quoted non-interpolated attribute values', ->
  eqJS '''
    <div a="""
      b
      c
    """ />
  ''', '''
    <div a={`b
    c`} />;
  '''

  eqJS """
    <div a='''
      b
      c
    ''' />
  """, '''
    <div a={`b
    c`} />;
  '''

</script>
<script type="text/x-literate-coffeescript" class="test" id="literate">
# Literate CoffeeScript Test

comment comment

    testsCount = 0 # Track the number of tests run in this file, to make sure they all run

    test "basic literate CoffeeScript parsing", ->
      ok yes
      testsCount++

now with a...

    test "broken up indentation", ->

... broken up ...

      do ->

... nested block.

        ok yes
        testsCount++

Code must be separated from text by a blank line.

    test "code blocks must be preceded by a blank line", ->

The next line is part of the text and will not be executed.
      fail()

      ok yes
      testsCount++

Code in `backticks is not parsed` and...

    test "comments in indented blocks work", ->
      do ->
        do ->
          # Regular comment.

          ###
            Block comment.
          ###

          ok yes
          testsCount++

Regular [Markdown](http://example.com/markdown) features, like links
and unordered lists, are fine:

  * I

  * Am

  * A

  * List

---

    # keep track of whether code blocks are executed or not
    executed = false

<p>

if true
  executed = true # should not execute, this is just HTML para, not code!

</p>

    test "should ignore code blocks inside HTML", ->
      eq executed, false
      testsCount++

---

*   A list item followed by a code block:

    test "basic literate CoffeeScript parsing", ->
      ok yes
      testsCount++

---

*   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
    Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
    viverra nec, fringilla in, laoreet vitae, risus.

*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
    Suspendisse id sem consectetuer libero luctus adipiscing.

---

This is [an example][id] reference-style link.
[id]: http://example.com/  "Optional Title Here"

---

    executed = no

1986. What a great season.

    executed = yes

and test...

    test "should recognize indented code blocks in lists with empty line as separator", ->
      ok executed
      testsCount++

---

    executed = no

1986\. What a great season.
           executed = yes

and test...

    test "should ignore indented code in escaped list like number", ->
      eq executed, no
      testsCount++

one last test!

    test "block quotes should render correctly", ->
      quote = '''
        foo
           and bar!
      '''
      eq quote, 'foo\n   and bar!'
      testsCount++

and finally, how did we do?

    test "all spaced literate CoffeeScript tests executed", ->
      eq testsCount, 9

</script>
<script type="text/x-literate-coffeescript" class="test" id="literate_tabbed">
# Tabbed Literate CoffeeScript Test

comment comment

	testsCount = 0 # Track the number of tests run in this file, to make sure they all run

	test "basic literate CoffeeScript parsing", ->
		ok yes
		testsCount++

now with a...

	test "broken up indentation", ->

... broken up ...

		do ->

... nested block.

			ok yes
			testsCount++

Code must be separated from text by a blank line.

	test "code blocks must be preceded by a blank line", ->

The next line is part of the text and will not be executed.
    fail()

		ok yes
		testsCount++

Code in `backticks is not parsed` and...

	test "comments in indented blocks work", ->
		do ->
			do ->
				# Regular comment.

				###
					Block comment.
				###

				ok yes
				testsCount++

Regular [Markdown](http://example.com/markdown) features, like links
and unordered lists, are fine:

  * I

  * Am

  * A

  * List

---

	# keep track of whether code blocks are executed or not
	executed = false

<p>

if true
	executed = true # should not execute, this is just HTML para, not code!

</p>

	test "should ignore code blocks inside HTML", ->
		eq executed, false
		testsCount++

---

*   A list item followed by a code block:

	test "basic literate CoffeeScript parsing", ->
		ok yes
		testsCount++

---

*   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
    Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
    viverra nec, fringilla in, laoreet vitae, risus.

*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
    Suspendisse id sem consectetuer libero luctus adipiscing.

---

This is [an example][id] reference-style link.
[id]: http://example.com/  "Optional Title Here"

---

	executed = no

1986. What a great season.

	executed = yes

and test...

	test "should recognize indented code blocks in lists with empty line as separator", ->
		ok executed
		testsCount++

---

	executed = no

1986\. What a great season.
				executed = yes

and test...

	test "should ignore indented code in escaped list like number", ->
		eq executed, no
		testsCount++

one last test!

	test "block quotes should render correctly", ->
		quote = '''
			foo
					and bar!
		'''
		eq quote, 'foo\n\t\tand bar!'
		testsCount++

and finally, how did we do?

	test "all tabbed literate CoffeeScript tests executed", ->
		eq testsCount, 9

</script>
<script type="text/x-coffeescript" class="test" id="location">
testScript = '''
if true
  x = 6
  console.log "A console #{x + 7} log"

foo = "bar"
z = /// ^ (a#{foo}) ///

x = () ->
    try
        console.log "foo"
    catch err
        # Rewriter will generate explicit indentation here.

    return null
'''

test "Verify location of generated tokens", ->
  tokens = CoffeeScript.tokens "a = 79"

  eq tokens.length, 4
  [aToken, equalsToken, numberToken] = tokens

  eq aToken[2].first_line, 0
  eq aToken[2].first_column, 0
  eq aToken[2].last_line, 0
  eq aToken[2].last_column, 0

  eq equalsToken[2].first_line, 0
  eq equalsToken[2].first_column, 2
  eq equalsToken[2].last_line, 0
  eq equalsToken[2].last_column, 2

  eq numberToken[2].first_line, 0
  eq numberToken[2].first_column, 4
  eq numberToken[2].last_line, 0
  eq numberToken[2].last_column, 5

test "Verify location of generated tokens (with indented first line)", ->
  tokens = CoffeeScript.tokens "  a = 83"

  eq tokens.length, 4
  [aToken, equalsToken, numberToken] = tokens

  eq aToken[2].first_line, 0
  eq aToken[2].first_column, 2
  eq aToken[2].last_line, 0
  eq aToken[2].last_column, 2
  eq aToken[2].range[0], 2
  eq aToken[2].range[1], 3

  eq equalsToken[2].first_line, 0
  eq equalsToken[2].first_column, 4
  eq equalsToken[2].last_line, 0
  eq equalsToken[2].last_column, 4

  eq numberToken[2].first_line, 0
  eq numberToken[2].first_column, 6
  eq numberToken[2].last_line, 0
  eq numberToken[2].last_column, 7

getMatchingTokens = (str, wantedTokens...) ->
  tokens = CoffeeScript.tokens str
  matchingTokens = []
  i = 0
  for token in tokens
    if token[1].replace(/^'|'$/g, '"') is wantedTokens[i]
      i++
      matchingTokens.push token
  eq wantedTokens.length, matchingTokens.length
  matchingTokens

test 'Verify locations in string interpolation (in "string")', ->
  [a, b, c] = getMatchingTokens '"a#{b}c"', '"a"', 'b', '"c"'

  eq a[2].first_line, 0
  eq a[2].first_column, 1
  eq a[2].last_line, 0
  eq a[2].last_column, 1

  eq b[2].first_line, 0
  eq b[2].first_column, 4
  eq b[2].last_line, 0
  eq b[2].last_column, 4

  eq c[2].first_line, 0
  eq c[2].first_column, 6
  eq c[2].last_line, 0
  eq c[2].last_column, 6

test 'Verify locations in string interpolation (in "string", multiple interpolation)', ->
  [a, b, c] = getMatchingTokens '"#{a}b#{c}"', 'a', '"b"', 'c'

  eq a[2].first_line, 0
  eq a[2].first_column, 3
  eq a[2].last_line, 0
  eq a[2].last_column, 3

  eq b[2].first_line, 0
  eq b[2].first_column, 5
  eq b[2].last_line, 0
  eq b[2].last_column, 5

  eq c[2].first_line, 0
  eq c[2].first_column, 8
  eq c[2].last_line, 0
  eq c[2].last_column, 8

test 'Verify locations in string interpolation (in "string", multiple interpolation and line breaks)', ->
  [a, b, c] = getMatchingTokens '"#{a}\nb\n#{c}"', 'a', '"\nb\n"', 'c'

  eq a[2].first_line, 0
  eq a[2].first_column, 3
  eq a[2].last_line, 0
  eq a[2].last_column, 3

  eq b[2].first_line, 0
  eq b[2].first_column, 5
  eq b[2].last_line, 1
  eq b[2].last_column, 1

  eq c[2].first_line, 2
  eq c[2].first_column, 2
  eq c[2].last_line, 2
  eq c[2].last_column, 2

test 'Verify locations in string interpolation (in "string", multiple interpolation and starting with line breaks)', ->
  [a, b, c] = getMatchingTokens '"\n#{a}\nb\n#{c}"', 'a', '"\nb\n"', 'c'

  eq a[2].first_line, 1
  eq a[2].first_column, 2
  eq a[2].last_line, 1
  eq a[2].last_column, 2

  eq b[2].first_line, 1
  eq b[2].first_column, 4
  eq b[2].last_line, 2
  eq b[2].last_column, 1

  eq c[2].first_line, 3
  eq c[2].first_column, 2
  eq c[2].last_line, 3
  eq c[2].last_column, 2

test 'Verify locations in string interpolation (in "string", multiple interpolation and starting with line breaks)', ->
  [a, b, c] = getMatchingTokens '"\n\n#{a}\n\nb\n\n#{c}"', 'a', '"\n\nb\n\n"', 'c'

  eq a[2].first_line, 2
  eq a[2].first_column, 2
  eq a[2].last_line, 2
  eq a[2].last_column, 2

  eq b[2].first_line, 2
  eq b[2].first_column, 4
  eq b[2].last_line, 5
  eq b[2].last_column, 0

  eq c[2].first_line, 6
  eq c[2].first_column, 2
  eq c[2].last_line, 6
  eq c[2].last_column, 2

test 'Verify locations in string interpolation (in "string", multiple interpolation and starting with line breaks)', ->
  [a, b, c] = getMatchingTokens '"\n\n\n#{a}\n\n\nb\n\n\n#{c}"', 'a', '"\n\n\nb\n\n\n"', 'c'

  eq a[2].first_line, 3
  eq a[2].first_column, 2
  eq a[2].last_line, 3
  eq a[2].last_column, 2

  eq b[2].first_line, 3
  eq b[2].first_column, 4
  eq b[2].last_line, 8
  eq b[2].last_column, 0

  eq c[2].first_line, 9
  eq c[2].first_column, 2
  eq c[2].last_line, 9
  eq c[2].last_column, 2

test 'Verify locations in string interpolation (in """string""", line breaks)', ->
  [a, b, c] = getMatchingTokens '"""a\n#{b}\nc"""', '"a\n"', 'b', '"\nc"'

  eq a[2].first_line, 0
  eq a[2].first_column, 3
  eq a[2].last_line, 0
  eq a[2].last_column, 4

  eq b[2].first_line, 1
  eq b[2].first_column, 2
  eq b[2].last_line, 1
  eq b[2].last_column, 2

  eq c[2].first_line, 1
  eq c[2].first_column, 4
  eq c[2].last_line, 2
  eq c[2].last_column, 0

test 'Verify locations in string interpolation (in """string""", starting with a line break)', ->
  [b, c] = getMatchingTokens '"""\n#{b}\nc"""', 'b', '"\nc"'

  eq b[2].first_line, 1
  eq b[2].first_column, 2
  eq b[2].last_line, 1
  eq b[2].last_column, 2

  eq c[2].first_line, 1
  eq c[2].first_column, 4
  eq c[2].last_line, 2
  eq c[2].last_column, 0

test 'Verify locations in string interpolation (in """string""", starting with line breaks)', ->
  [a, b, c] = getMatchingTokens '"""\n\n#{b}\nc"""', '"\n\n"', 'b', '"\nc"'

  eq a[2].first_line, 0
  eq a[2].first_column, 3
  eq a[2].last_line, 1
  eq a[2].last_column, 0

  eq b[2].first_line, 2
  eq b[2].first_column, 2
  eq b[2].last_line, 2
  eq b[2].last_column, 2

  eq c[2].first_line, 2
  eq c[2].first_column, 4
  eq c[2].last_line, 3
  eq c[2].last_column, 0

test 'Verify locations in string interpolation (in """string""", multiple interpolation)', ->
  [a, b, c] = getMatchingTokens '"""#{a}\nb\n#{c}"""', 'a', '"\nb\n"', 'c'

  eq a[2].first_line, 0
  eq a[2].first_column, 5
  eq a[2].last_line, 0
  eq a[2].last_column, 5

  eq b[2].first_line, 0
  eq b[2].first_column, 7
  eq b[2].last_line, 1
  eq b[2].last_column, 1

  eq c[2].first_line, 2
  eq c[2].first_column, 2
  eq c[2].last_line, 2
  eq c[2].last_column, 2

test 'Verify locations in string interpolation (in """string""", multiple interpolation, and starting with line breaks)', ->
  [a, b, c] = getMatchingTokens '"""\n\n#{a}\n\nb\n\n#{c}"""', 'a', '"\n\nb\n\n"', 'c'

  eq a[2].first_line, 2
  eq a[2].first_column, 2
  eq a[2].last_line, 2
  eq a[2].last_column, 2

  eq b[2].first_line, 2
  eq b[2].first_column, 4
  eq b[2].last_line, 5
  eq b[2].last_column, 0

  eq c[2].first_line, 6
  eq c[2].first_column, 2
  eq c[2].last_line, 6
  eq c[2].last_column, 2

test 'Verify locations in string interpolation (in """string""", multiple interpolation, and starting with line breaks)', ->
  [a, b, c] = getMatchingTokens '"""\n\n\n#{a}\n\n\nb\n\n\n#{c}"""', 'a', '"\n\n\nb\n\n\n"', 'c'

  eq a[2].first_line, 3
  eq a[2].first_column, 2
  eq a[2].last_line, 3
  eq a[2].last_column, 2

  eq b[2].first_line, 3
  eq b[2].first_column, 4
  eq b[2].last_line, 8
  eq b[2].last_column, 0

  eq c[2].first_line, 9
  eq c[2].first_column, 2
  eq c[2].last_line, 9
  eq c[2].last_column, 2

test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation)', ->
  [a, b, c] = getMatchingTokens '///#{a}b#{c}///', 'a', '"b"', 'c'

  eq a[2].first_line, 0
  eq a[2].first_column, 5
  eq a[2].last_line, 0
  eq a[2].last_column, 5

  eq b[2].first_line, 0
  eq b[2].first_column, 7
  eq b[2].last_line, 0
  eq b[2].last_column, 7

  eq c[2].first_line, 0
  eq c[2].first_column, 10
  eq c[2].last_line, 0
  eq c[2].last_column, 10

test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation)', ->
  [a, b, c] = getMatchingTokens '///a#{b}c///', '"a"', 'b', '"c"'

  eq a[2].first_line, 0
  eq a[2].first_column, 3
  eq a[2].last_line, 0
  eq a[2].last_column, 3

  eq b[2].first_line, 0
  eq b[2].first_column, 6
  eq b[2].last_line, 0
  eq b[2].last_column, 6

  eq c[2].first_line, 0
  eq c[2].first_column, 8
  eq c[2].last_line, 0
  eq c[2].last_column, 8

test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks)', ->
  [a, b, c] = getMatchingTokens '///#{a}\nb\n#{c}///', 'a', '"\nb\n"', 'c'

  eq a[2].first_line, 0
  eq a[2].first_column, 5
  eq a[2].last_line, 0
  eq a[2].last_column, 5

  eq b[2].first_line, 0
  eq b[2].first_column, 7
  eq b[2].last_line, 1
  eq b[2].last_column, 1

  eq c[2].first_line, 2
  eq c[2].first_column, 2
  eq c[2].last_line, 2
  eq c[2].last_column, 2

test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks)', ->
  [a, b, c] = getMatchingTokens '///#{a}\n\n\nb\n\n\n#{c}///', 'a', '"\n\n\nb\n\n\n"', 'c'

  eq a[2].first_line, 0
  eq a[2].first_column, 5
  eq a[2].last_line, 0
  eq a[2].last_column, 5

  eq b[2].first_line, 0
  eq b[2].first_column, 7
  eq b[2].last_line, 5
  eq b[2].last_column, 0

  eq c[2].first_line, 6
  eq c[2].first_column, 2
  eq c[2].last_line, 6
  eq c[2].last_column, 2

test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks)', ->
  [a, b, c] = getMatchingTokens '///a\n\n\n#{b}\n\n\nc///', '"a\n\n\n"', 'b', '"\n\n\nc"'

  eq a[2].first_line, 0
  eq a[2].first_column, 3
  eq a[2].last_line, 2
  eq a[2].last_column, 0

  eq b[2].first_line, 3
  eq b[2].first_column, 2
  eq b[2].last_line, 3
  eq b[2].last_column, 2

  eq c[2].first_line, 3
  eq c[2].first_column, 4
  eq c[2].last_line, 6
  eq c[2].last_column, 0

test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks and starting with linebreak)', ->
  [a, b, c] = getMatchingTokens '///\n#{a}\nb\n#{c}///', 'a', '"\nb\n"', 'c'

  eq a[2].first_line, 1
  eq a[2].first_column, 2
  eq a[2].last_line, 1
  eq a[2].last_column, 2

  eq b[2].first_line, 1
  eq b[2].first_column, 4
  eq b[2].last_line, 2
  eq b[2].last_column, 1

  eq c[2].first_line, 3
  eq c[2].first_column, 2
  eq c[2].last_line, 3
  eq c[2].last_column, 2

test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks and starting with linebreak)', ->
  [a, b, c] = getMatchingTokens '///\n\n\n#{a}\n\n\nb\n\n\n#{c}///', 'a', '"\n\n\nb\n\n\n"', 'c'

  eq a[2].first_line, 3
  eq a[2].first_column, 2
  eq a[2].last_line, 3
  eq a[2].last_column, 2

  eq b[2].first_line, 3
  eq b[2].first_column, 4
  eq b[2].last_line, 8
  eq b[2].last_column, 0

  eq c[2].first_line, 9
  eq c[2].first_column, 2
  eq c[2].last_line, 9
  eq c[2].last_column, 2

test 'Verify locations in heregex interpolation (in ///regex///, multiple interpolation and line breaks and starting with linebreak)', ->
  [a, b, c] = getMatchingTokens '///\n\n\na\n\n\n#{b}\n\n\nc///', '"\n\n\na\n\n\n"', 'b', '"\n\n\nc"'

  eq a[2].first_line, 0
  eq a[2].first_column, 3
  eq a[2].last_line, 5
  eq a[2].last_column, 0

  eq b[2].first_line, 6
  eq b[2].first_column, 2
  eq b[2].last_line, 6
  eq b[2].last_column, 2

  eq c[2].first_line, 6
  eq c[2].first_column, 4
  eq c[2].last_line, 9
  eq c[2].last_column, 0

test "#3822: Simple string/regex start/end should include delimiters", ->
  [stringToken] = CoffeeScript.tokens "'string'"
  eq stringToken[2].first_line, 0
  eq stringToken[2].first_column, 0
  eq stringToken[2].last_line, 0
  eq stringToken[2].last_column, 7

  [regexToken] = CoffeeScript.tokens "/regex/"
  eq regexToken[2].first_line, 0
  eq regexToken[2].first_column, 0
  eq regexToken[2].last_line, 0
  eq regexToken[2].last_column, 6

test "#3621: Multiline regex and manual `Regex` call with interpolation should
      result in the same tokens", ->
  tokensA = CoffeeScript.tokens '(RegExp(".*#{a}[0-9]"))'
  tokensB = CoffeeScript.tokens '///.*#{a}[0-9]///'
  eq tokensA.length, tokensB.length
  for i in [0...tokensA.length] by 1
    tokenA = tokensA[i]
    tokenB = tokensB[i]
    eq tokenA[0], tokenB[0] unless tokenB[0] in ['REGEX_START', 'REGEX_END']
    eq "#{tokenA[1]}", "#{tokenB[1]}"
    unless tokenA[0] is 'STRING_START' or tokenB[0] is 'REGEX_START'
      eq tokenA.origin?[1], tokenB.origin?[1]
    eq tokenA.stringEnd, tokenB.stringEnd

test "Verify tokens have locations that are in order", ->
  source = '''
    a {
      b: ->
        return c d,
          if e
            f
    }
    g
  '''
  tokens = CoffeeScript.tokens source
  lastToken = null
  for token in tokens
    if lastToken
      ok token[2].first_line >= lastToken[2].last_line
      if token[2].first_line == lastToken[2].last_line
        ok token[2].first_column >= lastToken[2].last_column
    lastToken = token

test "Verify OUTDENT tokens are located at the end of the previous token", ->
  source = '''
    SomeArr = [ ->
      if something
        lol =
          count: 500
    ]
  '''
  tokens = CoffeeScript.tokens source
  [..., number, curly, outdent1, outdent2, outdent3, bracket, terminator] = tokens
  eq number[0], 'NUMBER'
  for outdent in [outdent1, outdent2, outdent3]
    eq outdent[0], 'OUTDENT'
    eq outdent[2].first_line, number[2].last_line
    eq outdent[2].first_column, number[2].last_column
    eq outdent[2].last_line, number[2].last_line
    eq outdent[2].last_column, number[2].last_column

test "Verify OUTDENT and CALL_END tokens are located at the end of the previous token", ->
  source = '''
    a = b {
      c: ->
        d e,
          if f
            g {},
              if h
                i {}
    }
  '''
  tokens = CoffeeScript.tokens source
  [..., closeCurly1, callEnd1, outdent1, outdent2, callEnd2, outdent3, outdent4,
    callEnd3, outdent5, outdent6, closeCurly2, callEnd4, terminator] = tokens
  eq closeCurly1[0], '}'
  assertAtCloseCurly = (token) ->
    eq token[2].first_line, closeCurly1[2].last_line
    eq token[2].first_column, closeCurly1[2].last_column
    eq token[2].last_line, closeCurly1[2].last_line
    eq token[2].last_column, closeCurly1[2].last_column

  for token in [outdent1, outdent2, outdent3, outdent4, outdent5, outdent6]
    eq token[0], 'OUTDENT'
    assertAtCloseCurly(token)
  for token in [callEnd1, callEnd2, callEnd3]
    eq token[0], 'CALL_END'
    assertAtCloseCurly(token)

test "Verify generated } tokens are located at the end of the previous token", ->
  source = '''
    a(b, ->
      c: () ->
        if d
          e
    )
  '''
  tokens = CoffeeScript.tokens source
  [..., identifier, outdent1, outdent2, closeCurly, outdent3, callEnd,
    terminator] = tokens
  eq identifier[0], 'IDENTIFIER'
  assertAtIdentifier = (token) ->
    eq token[2].first_line, identifier[2].last_line
    eq token[2].first_column, identifier[2].last_column
    eq token[2].last_line, identifier[2].last_line
    eq token[2].last_column, identifier[2].last_column

  for token in [outdent1, outdent2, closeCurly, outdent3]
    assertAtIdentifier(token)

test "Verify real CALL_END tokens have the right position", ->
  source = '''
    a()
  '''
  tokens = CoffeeScript.tokens source
  [identifier, callStart, callEnd, terminator] = tokens
  startIndex = identifier[2].first_column
  eq identifier[2].last_column, startIndex
  eq callStart[2].first_column, startIndex + 1
  eq callStart[2].last_column, startIndex + 1
  eq callEnd[2].first_column, startIndex + 2
  eq callEnd[2].last_column, startIndex + 2

test "Verify normal heredocs have the right position", ->
  source = '''
    """
    a"""
  '''
  [stringToken] = CoffeeScript.tokens source
  eq stringToken[2].first_line, 0
  eq stringToken[2].first_column, 0
  eq stringToken[2].last_line, 1
  eq stringToken[2].last_column, 3

test "Verify heredocs ending with a newline have the right position", ->
  source = '''
    """
    a
    """
  '''
  [stringToken] = CoffeeScript.tokens source
  eq stringToken[2].first_line, 0
  eq stringToken[2].first_column, 0
  eq stringToken[2].last_line, 2
  eq stringToken[2].last_column, 2

test "Verify indented heredocs have the right position", ->
  source = '''
    ->
      """
        a
      """
  '''
  [arrow, indent, stringToken] = CoffeeScript.tokens source
  eq stringToken[2].first_line, 1
  eq stringToken[2].first_column, 2
  eq stringToken[2].last_line, 3
  eq stringToken[2].last_column, 4

test "Verify heregexes with interpolations have the right ending position", ->
  source = '''
    [a ///#{b}///g]
  '''
  [..., stringEnd, comma, flagsString, regexCallEnd, regexEnd, fnCallEnd,
    arrayEnd, terminator] = CoffeeScript.tokens source

  eq comma[0], ','
  eq arrayEnd[0], ']'

  assertColumn = (token, column, width = 0) ->
    eq token[2].first_line, 0
    eq token[2].first_column, column
    eq token[2].last_line, 0
    eq token[2].last_column, column
    eq token[2].last_column_exclusive, column + width

  arrayEndColumn = arrayEnd[2].first_column
  for token in [comma]
    assertColumn token, arrayEndColumn - 2
  for token in [flagsString, regexCallEnd]
    assertColumn token, arrayEndColumn - 1, 1
  for token in [regexEnd, fnCallEnd]
    assertColumn token, arrayEndColumn
  assertColumn arrayEnd, arrayEndColumn, 1

test "Verify all tokens get a location", ->
  doesNotThrow ->
    tokens = CoffeeScript.tokens testScript
    for token in tokens
        ok !!token[2]

test 'Values with properties end up with a location that includes the properties', ->
  source = '''
    a.b
    a.b.c
    a['b']
    a[b.c()].d
  '''
  {body: block} = CoffeeScript.nodes source
  [
    singleProperty
    twoProperties
    indexed
    complexIndex
  ] = block.expressions

  eq singleProperty.locationData.first_line, 0
  eq singleProperty.locationData.first_column, 0
  eq singleProperty.locationData.last_line, 0
  eq singleProperty.locationData.last_column, 2

  eq twoProperties.locationData.first_line, 1
  eq twoProperties.locationData.first_column, 0
  eq twoProperties.locationData.last_line, 1
  eq twoProperties.locationData.last_column, 4

  eq indexed.locationData.first_line, 2
  eq indexed.locationData.first_column, 0
  eq indexed.locationData.last_line, 2
  eq indexed.locationData.last_column, 5

  eq complexIndex.locationData.first_line, 3
  eq complexIndex.locationData.first_column, 0
  eq complexIndex.locationData.last_line, 3
  eq complexIndex.locationData.last_column, 9

test 'StringWithInterpolations::fromStringLiteral() assigns correct location to tagged template literal', ->
  checkLocationData = (source, {stringWithInterpolationsLocationData, stringLocationData}) ->
    {body: block} = CoffeeScript.nodes source
    taggedTemplateLiteral = block.expressions[0].unwrap()
    {args: [stringWithInterpolations]} = taggedTemplateLiteral
    {body} = stringWithInterpolations
    {expressions: [stringValue]} = body
    string = stringValue.unwrap()

    for field in ['first_line', 'first_column', 'last_line', 'last_column', 'last_line_exclusive', 'last_column_exclusive']
      eq stringWithInterpolations.locationData[field], stringWithInterpolationsLocationData[field]
      eq stringValue.locationData[field], stringLocationData[field]
      eq string.locationData[field], stringLocationData[field]

  checkLocationData 'a"b"',
    stringWithInterpolationsLocationData:
      first_line: 0
      first_column: 1
      last_line: 0
      last_column: 3
      last_line_exclusive: 0
      last_column_exclusive: 4
    stringLocationData:
      first_line: 0
      first_column: 2
      last_line: 0
      last_column: 2
      last_line_exclusive: 0
      last_column_exclusive: 3

  checkLocationData '''
    a"""
      b
    """
  ''',
    stringWithInterpolationsLocationData:
      first_line: 0
      first_column: 1
      last_line: 2
      last_column: 2
      last_line_exclusive: 2
      last_column_exclusive: 3
    stringLocationData:
      first_line: 0
      first_column: 4
      last_line: 1
      last_column: 3
      last_line_exclusive: 2
      last_column_exclusive: 0

  checkLocationData '''
    a"""b
    """
  ''',
    stringWithInterpolationsLocationData:
      first_line: 0
      first_column: 1
      last_line: 1
      last_column: 2
      last_line_exclusive: 1
      last_column_exclusive: 3
    stringLocationData:
      first_line: 0
      first_column: 4
      last_line: 0
      last_column: 5
      last_line_exclusive: 1
      last_column_exclusive: 0

test "Verify compound assignment operators have the right position", ->
  source = '''
    a or= b
  '''
  [a, operatorToken] = CoffeeScript.tokens source
  eq operatorToken[2].first_line, 0
  eq operatorToken[2].first_column, 2
  eq operatorToken[2].last_line, 0
  eq operatorToken[2].last_column, 4
  eq operatorToken[2].last_column_exclusive, 5
  eq operatorToken[2].range[0], 2
  eq operatorToken[2].range[1], 5

  source = '''
    a and= b
  '''
  [a, operatorToken] = CoffeeScript.tokens source
  eq operatorToken[2].first_line, 0
  eq operatorToken[2].first_column, 2
  eq operatorToken[2].last_line, 0
  eq operatorToken[2].last_column, 5
  eq operatorToken[2].last_column_exclusive, 6
  eq operatorToken[2].range[0], 2
  eq operatorToken[2].range[1], 6

test "Verify BOM is accounted for in location data", ->
  source = '''
    \ufeffa
    b
  '''
  [aToken, terminator, bToken] = CoffeeScript.tokens source
  eq aToken[2].first_line, 0
  eq aToken[2].first_column, 1
  eq aToken[2].last_line, 0
  eq aToken[2].last_column, 1
  eq aToken[2].last_column_exclusive, 2
  eq aToken[2].range[0], 1
  eq aToken[2].range[1], 2
  eq bToken[2].first_line, 1
  eq bToken[2].first_column, 0
  eq bToken[2].last_line, 1
  eq bToken[2].last_column, 0
  eq bToken[2].last_column_exclusive, 1
  eq bToken[2].range[0], 3
  eq bToken[2].range[1], 4

test "Verify carriage returns are accounted for in location data", ->
  source = '''
    a\r+
    b\r\r- c
  '''
  [aToken, plusToken, bToken, minusToken] = CoffeeScript.tokens source
  eq aToken[2].first_line, 0
  eq aToken[2].first_column, 0
  eq aToken[2].last_line, 0
  eq aToken[2].last_column, 0
  eq aToken[2].last_column_exclusive, 1
  eq aToken[2].range[0], 0
  eq aToken[2].range[1], 1
  eq plusToken[2].first_line, 0
  eq plusToken[2].first_column, 2
  eq plusToken[2].last_line, 0
  eq plusToken[2].last_column, 2
  eq plusToken[2].last_column_exclusive, 3
  eq plusToken[2].range[0], 2
  eq plusToken[2].range[1], 3
  eq bToken[2].first_line, 1
  eq bToken[2].first_column, 0
  eq bToken[2].last_line, 1
  eq bToken[2].last_column, 0
  eq bToken[2].last_column_exclusive, 1
  eq bToken[2].range[0], 4
  eq bToken[2].range[1], 5
  eq minusToken[2].first_line, 1
  eq minusToken[2].first_column, 3
  eq minusToken[2].last_line, 1
  eq minusToken[2].last_column, 3
  eq minusToken[2].last_column_exclusive, 4
  eq minusToken[2].range[0], 7
  eq minusToken[2].range[1], 8

test "Verify object colon location data", ->
  source = '''
    a : b
  '''
  [implicitBraceToken, aToken, colonToken] = CoffeeScript.tokens source
  eq colonToken[2].first_line, 0
  eq colonToken[2].first_column, 2
  eq colonToken[2].last_line, 0
  eq colonToken[2].last_column, 2
  eq colonToken[2].last_column_exclusive, 3
  eq colonToken[2].range[0], 2
  eq colonToken[2].range[1], 3

</script>
<script type="text/x-coffeescript" class="test" id="modules">
# Modules, a.k.a. ES2015 import/export
# ------------------------------------
#
# Remember, we’re not *resolving* modules, just outputting valid ES2015 syntax.


# This is the CoffeeScript import and export syntax, closely modeled after the ES2015 syntax
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export

# import "module-name"
# import defaultMember from "module-name"
# import * as name from "module-name"
# import { } from "module-name"
# import { member } from "module-name"
# import { member as alias } from "module-name"
# import { member1, member2 as alias2, … } from "module-name"
# import defaultMember, * as name from "module-name"
# import defaultMember, { … } from "module-name"

# export default expression
# export class name
# export { }
# export { name }
# export { name as exportedName }
# export { name as default }
# export { name1, name2 as exportedName2, name3 as default, … }
#
# export * from "module-name"
# export { … } from "module-name"
#
# As a subsitute for `export var name = …` and `export function name {}`,
# CoffeeScript also supports:
# export name = …

# CoffeeScript also supports optional commas within `{ … }`.


# Import statements

test "backticked import statement", ->
  eqJS """
    if Meteor.isServer
      `import { foo, bar as baz } from 'lib'`""",
  """
    if (Meteor.isServer) {
      import { foo, bar as baz } from 'lib';
    }"""

test "import an entire module for side effects only, without importing any bindings", ->
  eqJS "import 'lib'",
  "import 'lib';"

test "import default member from module, adding the member to the current scope", ->
  eqJS """
    import foo from 'lib'
    foo.fooMethod()""",
  """
    import foo from 'lib';

    foo.fooMethod();"""

test "import an entire module's contents as an alias, adding the alias to the current scope", ->
  eqJS """
    import * as foo from 'lib'
    foo.fooMethod()""",
  """
    import * as foo from 'lib';

    foo.fooMethod();"""

test "import empty object", ->
  eqJS "import { } from 'lib'",
  "import {} from 'lib';"

test "import empty object", ->
  eqJS "import {} from 'lib'",
  "import {} from 'lib';"

test "import a single member of a module, adding the member to the current scope", ->
  eqJS """
    import { foo } from 'lib'
    foo.fooMethod()""",
  """
    import {
      foo
    } from 'lib';

    foo.fooMethod();"""

test "import a single member of a module as an alias, adding the alias to the current scope", ->
  eqJS """
    import { foo as bar } from 'lib'
    bar.barMethod()""",
  """
    import {
      foo as bar
    } from 'lib';

    bar.barMethod();"""

test "import multiple members of a module, adding the members to the current scope", ->
  eqJS """
    import { foo, bar } from 'lib'
    foo.fooMethod()
    bar.barMethod()""",
  """
    import {
      foo,
      bar
    } from 'lib';

    foo.fooMethod();

    bar.barMethod();"""

test "import multiple members of a module where some are aliased, adding the members or aliases to the current scope", ->
  eqJS """
    import { foo, bar as baz } from 'lib'
    foo.fooMethod()
    baz.bazMethod()""",
  """
    import {
      foo,
      bar as baz
    } from 'lib';

    foo.fooMethod();

    baz.bazMethod();"""

test "import default member and other members of a module, adding the members to the current scope", ->
  eqJS """
    import foo, { bar, baz as qux } from 'lib'
    foo.fooMethod()
    bar.barMethod()
    qux.quxMethod()""",
  """
    import foo, {
      bar,
      baz as qux
    } from 'lib';

    foo.fooMethod();

    bar.barMethod();

    qux.quxMethod();"""

test "import default member from a module as well as the entire module's contents as an alias, adding the member and alias to the current scope", ->
  eqJS """
    import foo, * as bar from 'lib'
    foo.fooMethod()
    bar.barMethod()""",
  """
    import foo, * as bar from 'lib';

    foo.fooMethod();

    bar.barMethod();"""

test "multiline simple import", ->
  eqJS """
    import {
      foo,
      bar as baz
    } from 'lib'""",
  """
    import {
      foo,
      bar as baz
    } from 'lib';"""

test "multiline complex import", ->
  eqJS """
    import foo, {
      bar,
      baz as qux
    } from 'lib'""",
  """
    import foo, {
      bar,
      baz as qux
    } from 'lib';"""

test "import with optional commas", ->
  eqJS "import { foo, bar, } from 'lib'",
  """
    import {
      foo,
      bar
    } from 'lib';"""

test "multiline import without commas", ->
  eqJS """
    import {
      foo
      bar
    } from 'lib'""",
  """
    import {
      foo,
      bar
    } from 'lib';"""

test "multiline import with optional commas", ->
  eqJS """
    import {
      foo,
      bar,
    } from 'lib'""",
  """
    import {
      foo,
      bar
    } from 'lib';"""

test "a variable can be assigned after an import", ->
  eqJS """
    import { foo } from 'lib'
    bar = 5""",
  """
    var bar;

    import {
      foo
    } from 'lib';

    bar = 5;"""

test "variables can be assigned before and after an import", ->
  eqJS """
    foo = 5
    import { bar } from 'lib'
    baz = 7""",
  """
    var baz, foo;

    foo = 5;

    import {
      bar
    } from 'lib';

    baz = 7;"""

# Export statements

test "export empty object", ->
  eqJS "export { }",
  "export {};"

test "export empty object", ->
  eqJS "export {}",
  "export {};"

test "export named members within an object", ->
  eqJS "export { foo, bar }",
  """
    export {
      foo,
      bar
    };"""

test "export named members as aliases, within an object", ->
  eqJS "export { foo as bar, baz as qux }",
  """
    export {
      foo as bar,
      baz as qux
    };"""

test "export named members within an object, with an optional comma", ->
  eqJS "export { foo, bar, }",
  """
    export {
      foo,
      bar
    };"""

test "multiline export named members within an object", ->
  eqJS """
    export {
      foo,
      bar
    }""",
  """
    export {
      foo,
      bar
    };"""

test "multiline export named members within an object, with an optional comma", ->
  eqJS """
    export {
      foo,
      bar,
    }""",
  """
    export {
      foo,
      bar
    };"""

test "export default string", ->
  eqJS "export default 'foo'",
  "export default 'foo';"

test "export default number", ->
  eqJS "export default 5",
  "export default 5;"

test "export default object", ->
  eqJS "export default { foo: 'bar', baz: 'qux' }",
  """
    export default {
      foo: 'bar',
      baz: 'qux'
    };"""

test "export default implicit object", ->
  eqJS "export default foo: 'bar', baz: 'qux'",
  """
    export default {
      foo: 'bar',
      baz: 'qux'
    };"""

test "export default multiline implicit object", ->
  eqJS """
    export default
      foo: 'bar'
      baz: 'qux'
    """,
  """
    export default {
      foo: 'bar',
      baz: 'qux'
    };"""

test "export default multiline implicit object with internal braces", ->
  eqJS """
    export default
      foo: yes
      bar: {
        baz
      }
      quz: no
    """,
  """
    export default {
      foo: true,
      bar: {baz},
      quz: false
    };"""

test "export default assignment expression", ->
  eqJS "export default foo = 'bar'",
  """
    var foo;

    export default foo = 'bar';"""

test "export assignment expression", ->
  eqJS "export foo = 'bar'",
  "export var foo = 'bar';"

test "export multiline assignment expression", ->
  eqJS """
    export foo =
    'bar'""",
    "export var foo = 'bar';"

test "export multiline indented assignment expression", ->
  eqJS """
    export foo =
      'bar'""",
      "export var foo = 'bar';"

test "export default function", ->
  eqJS "export default ->",
  "export default function() {};"

test "export default multiline function", ->
  eqJS """
    export default (foo) ->
      console.log foo""",
    """
    export default function(foo) {
      return console.log(foo);
    };"""

test "export assignment function", ->
  eqJS """
    export foo = (bar) ->
      console.log bar""",
    """
    export var foo = function(bar) {
      return console.log(bar);
    };"""

test "export assignment function which contains assignments in its body", ->
  eqJS """
    export foo = (bar) ->
      baz = '!'
      console.log bar + baz""",
    """
    export var foo = function(bar) {
      var baz;
      baz = '!';
      return console.log(bar + baz);
    };"""

test "export default predefined function", ->
  eqJS """
    foo = (bar) ->
      console.log bar
    export default foo""",
  """
    var foo;

    foo = function(bar) {
      return console.log(bar);
    };

    export default foo;"""

test "export default class", ->
  eqJS """
    export default class foo extends bar
      baz: ->
        console.log 'hello, world!'""",
      """
    var foo;

    export default foo = class foo extends bar {
      baz() {
        return console.log('hello, world!');
      }

    };"""

test "export class", ->
  eqJS """
    export class foo
      baz: ->
        console.log 'hello, world!'""",
      """
    export var foo = class foo {
      baz() {
        return console.log('hello, world!');
      }

    };"""

test "export class that extends", ->
  eqJS """
    export class foo extends bar
      baz: ->
        console.log 'hello, world!'""",
      """
    export var foo = class foo extends bar {
      baz() {
        return console.log('hello, world!');
      }

    };"""

test "export default class that extends", ->
  eqJS """
    export default class foo extends bar
      baz: ->
        console.log 'hello, world!'""",
      """
    var foo;

    export default foo = class foo extends bar {
      baz() {
        return console.log('hello, world!');
      }

    };"""

test "export default named member, within an object", ->
  eqJS "export { foo as default, bar }",
  """
    export {
      foo as default,
      bar
    };"""

# Import and export in the same statement

test "export an entire module's contents", ->
  eqJS "export * from 'lib'",
  "export * from 'lib';"

test "export members imported from another module", ->
  eqJS "export { foo, bar } from 'lib'",
  """
    export {
      foo,
      bar
    } from 'lib';"""

test "export as aliases members imported from another module", ->
  eqJS "export { foo as bar, baz as qux } from 'lib'",
  """
    export {
      foo as bar,
      baz as qux
    } from 'lib';"""

test "export list can contain CoffeeScript keywords", ->
  eqJS "export { unless, and } from 'lib'",
  """
    export {
      unless,
      and
    } from 'lib';"""

test "export list can contain CoffeeScript keywords when aliasing", ->
  eqJS "export { when as bar, baz as unless, and as foo, booze as not } from 'lib'",
  """
    export {
      when as bar,
      baz as unless,
      and as foo,
      booze as not
    } from 'lib';"""


# Edge cases

test "multiline import with comments", ->
  eqJS """
    import {
      foo, # Not as good as bar
      bar as baz # I prefer qux
    } from 'lib'""",
  """
    import {
      foo, // Not as good as bar
      bar as baz // I prefer qux
    } from 'lib';"""

test "`from` not part of an import or export statement can still be assigned", ->
  from = 5
  eq 5, from

test "a variable named `from` can be assigned after an import", ->
  eqJS """
    import { foo } from 'lib'
    from = 5""",
  """
    var from;

    import {
      foo
    } from 'lib';

    from = 5;"""

test "`from` can be assigned after a multiline import", ->
  eqJS """
    import {
      foo
    } from 'lib'
    from = 5""",
  """
    var from;

    import {
      foo
    } from 'lib';

    from = 5;"""

test "`from` can be imported as a member name", ->
  eqJS "import { from } from 'lib'",
  """
    import {
      from
    } from 'lib';"""

test "`from` can be imported as a member name and aliased", ->
  eqJS "import { from as foo } from 'lib'",
  """
    import {
      from as foo
    } from 'lib';"""

test "`from` can be used as an alias name", ->
  eqJS "import { foo as from } from 'lib'",
  """
    import {
      foo as from
    } from 'lib';"""

test "`as` can be imported as a member name", ->
  eqJS "import { as } from 'lib'",
  """
    import {
      as
    } from 'lib';"""

test "`as` can be imported as a member name and aliased", ->
  eqJS "import { as as foo } from 'lib'",
  """
    import {
      as as foo
    } from 'lib';"""

test "`as` can be used as an alias name", ->
  eqJS "import { foo as as } from 'lib'",
  """
    import {
      foo as as
    } from 'lib';"""

test "CoffeeScript keywords can be used as imported names in import lists", ->
  eqJS """
    import { unless as bar, and as computedAnd } from 'lib'
    bar.barMethod()""",
  """
    import {
      unless as bar,
      and as computedAnd
    } from 'lib';

    bar.barMethod();"""

test "`*` can be used in an expression on the same line as an export keyword", ->
  eqJS "export foo = (x) -> x * x",
  """
    export var foo = function(x) {
      return x * x;
    };"""
  eqJS "export default foo = (x) -> x * x",
  """
    var foo;

    export default foo = function(x) {
      return x * x;
    };"""

test "`*` and `from` can be used in an export default expression", ->
  eqJS """
    export default foo.extend
      bar: ->
        from = 5
        from = from * 3""",
      """
    export default foo.extend({
      bar: function() {
        var from;
        from = 5;
        return from = from * 3;
      }
    });"""

test "wrapped members can be imported multiple times if aliased", ->
  eqJS "import { foo, foo as bar } from 'lib'",
  """
    import {
      foo,
      foo as bar
    } from 'lib';"""

test "default and wrapped members can be imported multiple times if aliased", ->
  eqJS "import foo, { foo as bar } from 'lib'",
  """
    import foo, {
      foo as bar
    } from 'lib';"""

test "import a member named default", ->
  eqJS "import { default } from 'lib'",
  """
    import {
      default
    } from 'lib';"""

test "import an aliased member named default", ->
  eqJS "import { default as def } from 'lib'",
  """
    import {
      default as def
    } from 'lib';"""

test "export a member named default", ->
  eqJS "export { default }",
  """
    export {
      default
    };"""

test "export an aliased member named default", ->
  eqJS "export { def as default }",
  """
    export {
      def as default
    };"""

test "import an imported member named default", ->
  eqJS "import { default } from 'lib'",
  """
    import {
      default
    } from 'lib';"""

test "import an imported aliased member named default", ->
  eqJS "import { default as def } from 'lib'",
  """
    import {
      default as def
    } from 'lib';"""

test "export an imported member named default", ->
  eqJS "export { default } from 'lib'",
  """
    export {
      default
    } from 'lib';"""

test "export an imported aliased member named default", ->
  eqJS "export { default as def } from 'lib'",
  """
    export {
      default as def
    } from 'lib';"""

test "#4394: export shouldn't prevent variable declarations", ->
  eqJS """
    x = 1
    export { x }
  """,
  """
    var x;

    x = 1;

    export {
      x
    };
  """

test "#4451: `default` in an export statement is only treated as a keyword when it follows `export` or `as`", ->
  eqJS "export default { default: 1 }",
  """
    export default {
      default: 1
    };
  """

test "#4491: import- and export-specific lexing should stop after import/export statement", ->
  eqJS """
    import {
      foo,
      bar as baz
    } from 'lib'

    foo as
    3 * as 4
    from 'foo'
    """,
  """
    import {
      foo,
      bar as baz
    } from 'lib';

    foo(as);

    3 * as(4);

    from('foo');
    """

  eqJS """
    import { foo, bar as baz } from 'lib'

    foo as
    3 * as 4
    from 'foo'
    """,
  """
    import {
      foo,
      bar as baz
    } from 'lib';

    foo(as);

    3 * as(4);

    from('foo');
    """

  eqJS """
    import * as lib from 'lib'

    foo as
    3 * as 4
    from 'foo'
    """,
  """
    import * as lib from 'lib';

    foo(as);

    3 * as(4);

    from('foo');
    """

  eqJS """
    export {
      foo,
      bar
    }

    foo as
    3 * as 4
    from 'foo'
    """,
  """
    export {
      foo,
      bar
    };

    foo(as);

    3 * as(4);

    from('foo');
    """

  eqJS """
    export * from 'lib'

    foo as
    3 * as 4
    from 'foo'
    """,
  """
    export * from 'lib';

    foo(as);

    3 * as(4);

    from('foo');
    """

# Issue #4874: Backslash not supported in import or export statements
test "#4874: backslash `import`", ->

  eqJS """
    import foo \
        from 'lib'

    foo a
    """,
  """
    import foo from 'lib';

    foo(a);
    """

  eqJS """
    import \
                    foo \
        from \
    'lib'

    foo a
    """,
  """
    import foo from 'lib';

    foo(a);
    """

  eqJS """
    import \
          utilityBelt \
    , {
      each
    } from \
    'underscore'
    """,
  """
    import utilityBelt, {
      each
    } from 'underscore';
    """

test "#4874: backslash `export`", ->
  eqJS """
    export \
      * \
            from \
      'underscore'
    """,
  """
    export * from 'underscore';
    """

  eqJS """
    export \
        { max, min } \
              from \
      'underscore'
  """,
  """
    export {
      max,
      min
    } from 'underscore';
    """

test "#4834: dynamic import", ->
  eqJS """
    import('module').then ->
  """,
  """
    import('module').then(function() {});
  """

  eqJS """
    foo = ->
      bar = await import('bar')
  """,
  """
    var foo;

    foo = async function() {
      var bar;
      return bar = (await import('bar'));
    };
  """

  eqJS """
    console.log import('foo')
  """,
  """
    console.log(import('foo'));
  """

test "#5317: Support import.meta", ->
  eqJS """
    foo = import.meta
  """,
  """
    var foo;

    foo = import.meta;
  """

  eqJS """
    foo = import
        .meta
  """,
  """
    var foo;

    foo = import.meta;
  """

  eqJS """
    foo = import.
        meta
  """,
  """
    var foo;

    foo = import.meta;
  """

  eqJS """
    foo = import.meta.bar
  """,
  """
    var foo;

    foo = import.meta.bar;
  """

  eqJS """
    foo = import
        .meta
        .bar
  """,
  """
    var foo;

    foo = import.meta.bar;
  """

  eqJS """
    console.log import.meta.url
  """,
  """
    console.log(import.meta.url);
  """

</script>
<script type="text/x-coffeescript" class="test" id="numbers">
# Number Literals
# ---------------

# * Decimal Integer Literals
# * Octal Integer Literals
# * Hexadecimal Integer Literals
# * Scientific Notation Integer Literals
# * Scientific Notation Non-Integer Literals
# * Non-Integer Literals
# * Binary Integer Literals


# Binary Integer Literals
# Binary notation is understood as would be decimal notation.

test "Parser recognises binary numbers", ->
  eq 4, 0b100

# Decimal Integer Literals

test "call methods directly on numbers", ->
  eq 4, 4.valueOf()
  eq '11', 4.toString 3
  eq '1000', 1_000.toString()

eq -1, 3 -4

#764: Numbers should be indexable
eq Number::toString, 42['toString']

eq Number::toString, 42.toString

eq Number::toString, 2e308['toString'] # Infinity


# Non-Integer Literals

# Decimal number literals.
value = .25 + .75
ok value is 1
value = 0.0 + -.25 - -.75 + 0.0
ok value is 0.5

#764: Numbers should be indexable
eq Number::toString,   4['toString']
eq Number::toString, 4.2['toString']
eq Number::toString, .42['toString']
eq Number::toString, (4)['toString']

eq Number::toString,   4.toString
eq Number::toString, 4.2.toString
eq Number::toString, .42.toString
eq Number::toString, (4).toString

test '#1168: leading floating point suppresses newline', ->
  eq 1, do ->
    1
    .5 + 0.5

test "Python-style octal literal notation '0o777'", ->
  eq 511, 0o777
  eq 1, 0o1
  eq 1, 0o00001
  eq parseInt('0777', 8), 0o777
  eq '777', 0o777.toString 8
  eq 4, 0o4.valueOf()
  eq Number::toString, 0o777['toString']
  eq Number::toString, 0o777.toString

test "#2060: Disallow uppercase radix prefixes", ->
  for char in ['b', 'o', 'x']
    program = "0#{char}0"
    doesNotThrowCompileError program, bare: yes
    throwsCompileError program.toUpperCase(), bare: yes

test "#5164: Allow lowercase and uppercase exponent notation", ->
  doesNotThrow -> CoffeeScript.compile "0e0", bare: yes
  doesNotThrow -> CoffeeScript.compile "0E0", bare: yes

test "#2224: hex literals with 0b or B or E", ->
  eq 176, 0x0b0
  eq 177, 0x0B1
  eq 225, 0xE1

test "Infinity", ->
  eq Infinity, CoffeeScript.eval "0b#{Array(1024 + 1).join('1')}"
  eq Infinity, CoffeeScript.eval "0o#{Array(342 + 1).join('7')}"
  eq Infinity, CoffeeScript.eval "0x#{Array(256 + 1).join('f')}"
  eq Infinity, CoffeeScript.eval Array(500 + 1).join('9')
  eq Infinity, 2e308

test "NaN", ->
  ok isNaN 1/NaN

</script>
<script type="text/x-coffeescript" class="test" id="numbers_bigint">
# BigInt Literals
# ---------------

test "BigInt exists", ->
  'object' is typeof BigInt

test "Parser recognizes decimal BigInt literals", ->
  eq 42n, BigInt 42

test "Parser recognizes decimal BigInt literals with separator", ->
  eq 1_000n, BigInt 1000

test "Parser recognizes binary BigInt literals", ->
  eq 42n, 0b101010n

test "Parser recognizes octal BigInt literals", ->
  eq 42n, 0o52n

test "Parser recognizes hexadecimal BigInt literals", ->
  eq 42n, 0x2an

</script>
<script type="text/x-coffeescript" class="test" id="numeric_literal_separators">
# Numeric Literal Separators
# --------------------------

test 'integer literals with separators', ->
  eq 123_456, 123456
  eq 12_34_56, 123456

test 'decimal literals with separators', ->
  eq 1_2.34_5, 12.345
  eq 1_0e1_0, 10e10
  eq 1_2.34_5e6_7, 12.345e67

test 'hexadecimal literals with separators', ->
  eq 0x1_2_3_4, 0x1234

test 'binary literals with separators', ->
  eq 0b10_10, 0b1010

test 'octal literals with separators', ->
  eq 0o7_7_7, 0o777

test 'infinity with separator', ->
  eq 2e3_08, Infinity

test 'range with separators', ->
  range = [10_000...10_002]
  eq range.length, 2
  eq range[0], 10000

test 'property access on a number', ->
  # Somehow, `3..toFixed()` is valid JavaScript; though just `3.toFixed()`
  # is not. CoffeeScript has long allowed code like `3.toFixed()` to compile
  # into `3..toFixed()`.
  eq 3.toFixed(), '3'
  # Where this can conflict with numeric literal separators is when the
  # property name contains an underscore.
  Number::_23 = _23 = 'x'
  eq 1._23, 'x'
  ok 1._34 is undefined
  delete Number::_23

test 'invalid decimal literal separators do not compile', ->
  # `1._23` is a valid property access (see previous test)
  throwsCompileError '1_.23'
  throwsCompileError '1e_2'
  throwsCompileError '1e2_'
  throwsCompileError '1_'
  throwsCompileError '1__2'

test 'invalid hexadecimal literal separators do not compile', ->
  throwsCompileError '0x_1234'
  throwsCompileError '0x1234_'
  throwsCompileError '0x1__34'

test 'invalid binary literal separators do not compile', ->
  throwsCompileError '0b_100'
  throwsCompileError '0b100_'
  throwsCompileError '0b1__1'

test 'invalid octal literal separators do not compile', ->
  throwsCompileError '0o_777'
  throwsCompileError '0o777_'
  throwsCompileError '0o6__6'

</script>
<script type="text/x-coffeescript" class="test" id="object_rest_spread">
test "#4798 destructuring of objects with splat within arrays", ->
  arr = [1, {a:1, b:2}]
  [...,{a, r...}] = arr
  eq a, 1
  deepEqual r, {b:2}
  [b, {q...}] = arr
  eq b, 1
  deepEqual q, arr[1]
  eq q.b, r.b
  eq q.a, a

  arr2 = [arr[1]]
  [{a2...}] = arr2
  eq a2.a, arr2[0].a

test "destructuring assignment with objects and splats: ES2015", ->
  obj = {a: 1, b: 2, c: 3, d: 4, e: 5}
  throwsCompileError "{a, r..., s...} = x", null, null, "multiple rest elements are disallowed"
  throwsCompileError "{a, r..., s..., b} = x", null, null, "multiple rest elements are disallowed"
  prop = "b"
  {a, b, r...} = obj
  eq a, 1
  eq b, 2
  eq r.e, obj.e
  eq r.a, undefined
  {d, c: x, r...} = obj
  eq x, 3
  eq d, 4
  eq r.c, undefined
  eq r.b, 2
  {a, 'b': z, g = 9, r...} = obj
  eq g, 9
  eq z, 2
  eq r.b, undefined

test "destructuring assignment with splats and default values", ->
  obj = {}
  c = {b: 1}
  { a: {b} = c, d...} = obj

  eq b, 1
  deepEqual d, {}

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  {
    a: {b} = c
    d ...
  } = obj

  eq b, 1
  deepEqual d, {}

test "destructuring assignment with splat with default value", ->
  obj = {}
  c = {val: 1}
  { a: {b...} = c } = obj

  deepEqual b, val: 1

test "destructuring assignment with multiple splats in different objects", ->
  obj = { a: {val: 1}, b: {val: 2} }
  { a: {a...}, b: {b...} } = obj
  deepEqual a, val: 1
  deepEqual b, val: 2

  o = {
    props: {
      p: {
        n: 1
        m: 5
      }
      s: 6
    }
  }
  {p: {m, q..., t = {obj...}}, r...} = o.props
  eq m, o.props.p.m
  deepEqual r, s: 6
  deepEqual q, n: 1
  deepEqual t, obj

  @props = o.props
  {p: {m}, r...} = @props
  eq m, @props.p.m
  deepEqual r, s: 6

  {p: {m}, r...} = {o.props..., p:{m:9}}
  eq m, 9

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  {
    a: {
      a ...
    }
    b: {
      b ...
    }
  } = obj
  deepEqual a, val: 1
  deepEqual b, val: 2

test "destructuring assignment with dynamic keys and splats", ->
  i = 0
  foo = -> ++i

  obj = {1: 'a', 2: 'b'}
  { "#{foo()}": a, b... } = obj

  eq a, 'a'
  eq i, 1
  deepEqual b, 2: 'b'

# Tests from https://babeljs.io/docs/plugins/transform-object-rest-spread/.
test "destructuring assignment with objects and splats: Babel tests", ->
  # What Babel calls “rest properties:”
  { x, y, z... } = { x: 1, y: 2, a: 3, b: 4 }
  eq x, 1
  eq y, 2
  deepEqual z, { a: 3, b: 4 }

  # What Babel calls “spread properties:”
  n = { x, y, z... }
  deepEqual n, { x: 1, y: 2, a: 3, b: 4 }

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  { x, y, z ... } = { x: 1, y: 2, a: 3, b: 4 }
  eq x, 1
  eq y, 2
  deepEqual z, { a: 3, b: 4 }

  n = { x, y, z ... }
  deepEqual n, { x: 1, y: 2, a: 3, b: 4 }

test "deep destructuring assignment with objects: ES2015", ->
  a1={}; b1={}; c1={}; d1={}
  obj = {
    a: a1
    b: {
      'c': {
        d: {
          b1
          e: c1
          f: d1
        }
      }
    }
    b2: {b1, c1}
  }
  {a: w, b: {c: {d: {b1: bb, r1...}}}, r2...} = obj
  eq r1.e, c1
  eq r2.b, undefined
  eq bb, b1
  eq r2.b2, obj.b2

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  {a: w, b: {c: {d: {b1: bb, r1 ...}}}, r2 ...} = obj
  eq r1.e, c1
  eq r2.b, undefined
  eq bb, b1
  eq r2.b2, obj.b2

test "deep destructuring assignment with defaults: ES2015", ->
  obj =
    b: { c: 1, baz: 'qux' }
    foo: 'bar'
  j =
    f: 'world'
  i =
    some: 'prop'
  {
    a...
    b: { c, d... }
    e: {
      f: hello
      g: { h... } = i
    } = j
  } = obj

  deepEqual a, foo: 'bar'
  eq c, 1
  deepEqual d, baz: 'qux'
  eq hello, 'world'
  deepEqual h, some: 'prop'

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  {
    a ...
    b: {
      c,
      d ...
    }
    e: {
      f: hello
      g: {
        h ...
      } = i
    } = j
  } = obj

  deepEqual a, foo: 'bar'
  eq c, 1
  deepEqual d, baz: 'qux'
  eq hello, 'world'
  deepEqual h, some: 'prop'

test "object spread properties: ES2015", ->
  obj = {a: 1, b: 2, c: 3, d: 4, e: 5}
  obj2 = {obj..., c:9}
  eq obj2.c, 9
  eq obj.a, obj2.a

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  obj2 = {
    obj ...
    c:9
  }
  eq obj2.c, 9
  eq obj.a, obj2.a

  obj2 = {obj..., a: 8, c: 9, obj...}
  eq obj2.c, 3
  eq obj.a, obj2.a

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  obj2 = {
    obj ...
    a: 8
    c: 9
    obj ...
  }
  eq obj2.c, 3
  eq obj.a, obj2.a

  obj3 = {obj..., b: 7, g: {obj2..., c: 1}}
  eq obj3.g.c, 1
  eq obj3.b, 7
  deepEqual obj3.g, {obj..., c: 1}

  (({a, b, r...}) ->
    eq 1, a
    deepEqual r, {c: 3, d: 44, e: 55}
  ) {obj2..., d: 44, e: 55}

  obj = {a: 1, b: 2, c: {d: 3, e: 4, f: {g: 5}}}
  obj4 = {a: 10, obj.c...}
  eq obj4.a, 10
  eq obj4.d, 3
  eq obj4.f.g, 5
  deepEqual obj4.f, obj.c.f

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  (({
    a
    b
    r ...
    }) ->
    eq 1, a
    deepEqual r, {c: 3, d: 44, e: 55}
  ) {
    obj2 ...
    d: 44
    e: 55
  }

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  obj4 = {
    a: 10
    obj.c ...
  }
  eq obj4.a, 10
  eq obj4.d, 3
  eq obj4.f.g, 5
  deepEqual obj4.f, obj.c.f

  obj5 = {obj..., ((k) -> {b: k})(99)...}
  eq obj5.b, 99
  deepEqual obj5.c, obj.c

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  obj5 = {
    obj ...
    ((k) -> {b: k})(99) ...
  }
  eq obj5.b, 99
  deepEqual obj5.c, obj.c

  fn = -> {c: {d: 33, e: 44, f: {g: 55}}}
  obj6 = {obj..., fn()...}
  eq obj6.c.d, 33
  deepEqual obj6.c, {d: 33, e: 44, f: {g: 55}}

  obj7 = {obj..., fn()..., {c: {d: 55, e: 66, f: {77}}}...}
  eq obj7.c.d, 55
  deepEqual obj6.c, {d: 33, e: 44, f: {g: 55}}

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  obj7 = {
    obj ...
    fn() ...
    {c: {d: 55, e: 66, f: {77}}} ...
  }
  eq obj7.c.d, 55
  deepEqual obj6.c, {d: 33, e: 44, f: {g: 55}}

  obj =
    a:
      b:
        c:
          d:
            e: {}
  obj9 = {a:1, obj.a.b.c..., g:3}
  deepEqual obj9.d, {e: {}}

  a = "a"
  c = "c"
  obj9 = {a:1, obj[a].b[c]..., g:3}
  deepEqual obj9.d, {e: {}}

  obj9 = {a:1, obj.a["b"].c["d"]..., g:3}
  deepEqual obj9["e"], {}

test "#4673: complex destructured object spread variables", ->
  b = c: 1
  {{a...}...} = b
  eq a.c, 1

  d = {}
  {d.e...} = f: 1
  eq d.e.f, 1

  {{g}...} = g: 1
  eq g, 1

test "rest element destructuring in function definition", ->
  obj = {a: 1, b: 2, c: 3, d: 4, e: 5}

  (({a, b, r...}) ->
    eq 1, a
    eq 2, b,
    deepEqual r, {c: 3, d: 4, e: 5}
  ) obj

  (({a: p, b, r...}, q) ->
    eq p, 1
    eq q, 9
    deepEqual r, {c: 3, d: 4, e: 5}
  ) {a:1, b:2, c:3, d:4, e:5}, 9

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  (({
      a: p
      b
      r ...
    }, q) ->
    eq p, 1
    eq q, 9
    deepEqual r, {c: 3, d: 4, e: 5}
  ) {a:1, b:2, c:3, d:4, e:5}, 9

  a1={}; b1={}; c1={}; d1={}
  obj1 = {
    a: a1
    b: {
      'c': {
        d: {
          b1
          e: c1
          f: d1
        }
      }
    }
    b2: {b1, c1}
  }

  (({a: w, b: {c: {d: {b1: bb, r1...}}}, r2...}) ->
    eq a1, w
    eq bb, b1
    eq r2.b, undefined
    deepEqual r1, {e: c1, f: d1}
    deepEqual r2.b2, {b1, c1}
  ) obj1

  b = 3
  f = ({a, b...}) ->
  f {}
  eq 3, b

  (({a, r...} = {}) ->
    eq a, undefined
    deepEqual r, {}
  )()

  (({a, r...} = {}) ->
    eq a, 1
    deepEqual r, {b: 2, c: 3}
  ) {a: 1, b: 2, c: 3}

  f = ({a, r...} = {}) -> [a, r]
  deepEqual [undefined, {}], f()
  deepEqual [1, {b: 2}], f {a: 1, b: 2}
  deepEqual [1, {}], f {a: 1}

  f = ({a, r...} = {a: 1, b: 2}) -> [a, r]
  deepEqual [1, {b:2}], f()
  deepEqual [2, {}], f {a:2}
  deepEqual [3, {c:5}], f {a:3, c:5}

  f = ({ a: aa = 0, b: bb = 0 }) -> [aa, bb]
  deepEqual [0, 0], f {}
  deepEqual [0, 42], f {b:42}
  deepEqual [42, 0], f {a:42}
  deepEqual [42, 43], f {a:42, b:43}

test "#4673: complex destructured object spread variables", ->
  f = ({{a...}...}) ->
    a
  eq f(c: 1).c, 1

  g = ({@y...}) ->
    eq @y.b, 1
  g b: 1

test "#4834: dynamic import can technically be object spread", ->
  eqJS """
    x = {...import('module')}
  """,
  """
    var x;

    x = {...import('module')};
  """

test "#5168: allow indented property index", ->
  a = b: c: 3

  eq 3, {
    ...a[
      if yes
        'b'
      else
        'c'
    ]
  }.c

test "#5291: soaks/prototype shorthands in object spread variables", ->
  withPrototype =
    prototype:
      b: {c: 1}
  eq {withPrototype::b...}.c, 1
  eq {...withPrototype::b}.c, 1

  withSoak =
    b:
      c: 2
  eq {withSoak?.b...}.c, 2
  eq {...withSoak?.b}.c, 2

  soakedCall = ->
    b:
      c: 3
  eq {soakedCall?().b...}.c, 3
  eq {...soakedCall?().b}.c, 3

  assignToPrototype =
    prototype: {}
  {...assignToPrototype::b} = c: 4
  eq assignToPrototype::b.c, 4

</script>
<script type="text/x-coffeescript" class="test" id="objects">
# Object Literals
# ---------------

# TODO: refactor object literal tests
# TODO: add indexing and method invocation tests: {a}['a'] is a, {a}.a()

trailingComma = {k1: "v1", k2: 4, k3: (-> true),}
ok trailingComma.k3() and (trailingComma.k2 is 4) and (trailingComma.k1 is "v1")

ok {a: (num) -> num is 10 }.a 10

moe = {
  name:  'Moe'
  greet: (salutation) ->
    salutation + " " + @name
  hello: ->
    @['greet'] "Hello"
  10: 'number'
}
ok moe.hello() is "Hello Moe"
ok moe[10] is 'number'
moe.hello = ->
  this['greet'] "Hello"
ok moe.hello() is 'Hello Moe'

obj = {
  is:     -> yes,
  'not':  -> no,
}
ok obj.is()
ok not obj.not()

### Top-level object literal... ###
obj: 1
### ...doesn't break things. ###

# Object literals should be able to include keywords.
obj = {class: 'höt'}
obj.function = 'dog'
ok obj.class + obj.function is 'hötdog'

# Implicit objects as part of chained calls.
pluck = (x) -> x.a
eq 100, pluck pluck pluck a: a: a: 100


test "YAML-style object literals", ->
  obj =
    a: 1
    b: 2
  eq 1, obj.a
  eq 2, obj.b

  config =
    development:
      server: 'localhost'
      timeout: 10

    production:
      server: 'dreamboat'
      timeout: 1000

  ok config.development.server  is 'localhost'
  ok config.production.server   is 'dreamboat'
  ok config.development.timeout is 10
  ok config.production.timeout  is 1000

obj =
  a: 1,
  b: 2,
ok obj.a is 1
ok obj.b is 2

# Implicit objects nesting.
obj =
  options:
    value: yes
  fn: ->
    {}
    null
ok obj.options.value is yes
ok obj.fn() is null

# Implicit objects with wacky indentation:
obj =
  'reverse': (obj) ->
    Array.prototype.reverse.call obj
  abc: ->
    @reverse(
      @reverse @reverse ['a', 'b', 'c'].reverse()
    )
  one: [1, 2,
    a: 'b'
  3, 4]
  red:
    orange:
          yellow:
                  green: 'blue'
    indigo: 'violet'
  misdent: [[],
  [],
                  [],
      []]
ok obj.abc().join(' ') is 'a b c'
ok obj.one.length is 5
ok obj.one[4] is 4
ok obj.one[2].a is 'b'
ok (key for key of obj.red).length is 2
ok obj.red.orange.yellow.green is 'blue'
ok obj.red.indigo is 'violet'
ok obj.misdent.toString() is ',,,'

#542: Objects leading expression statement should be parenthesized.
{f: -> ok yes }.f() + 1

# String-keyed objects shouldn't suppress newlines.
one =
  '>!': 3
six: -> 10
ok not one.six

# Shorthand objects with property references.
obj =
  ### comment one ###
  ### comment two ###
  one: 1
  two: 2
  object: -> {@one, @two}
  list:   -> [@one, @two]
result = obj.object()
eq result.one, 1
eq result.two, 2
eq result.two, obj.list()[1]

third = (a, b, c) -> c
obj =
  one: 'one'
  two: third 'one', 'two', 'three'
ok obj.one is 'one'
ok obj.two is 'three'

test "invoking functions with implicit object literals", ->
  generateGetter = (prop) -> (obj) -> obj[prop]
  getA = generateGetter 'a'
  getArgs = -> arguments
  a = b = 30

  result = getA
    a: 10
  eq 10, result

  result = getA
    "a": 20
  eq 20, result

  result = getA a,
    b:1
  eq undefined, result

  result = getA b:1,
  a:43
  eq 43, result

  result = getA b:1,
    a:62
  eq undefined, result

  result = getA
    b:1
    a
  eq undefined, result

  result = getA
    a:
      b:2
    b:1
  eq 2, result.b

  result = getArgs
    a:1
    b
    c:1
  ok result.length is 3
  ok result[2].c is 1

  result = getA b: 13, a: 42, 2
  eq 42, result

  result = getArgs a:1, (1 + 1)
  ok result[1] is 2

  result = getArgs a:1, b
  ok result.length is 2
  ok result[1] is 30

  result = getArgs a:1, b, b:1, a
  ok result.length is 4
  ok result[2].b is 1

  throwsCompileError "a = b:1, c"

test "some weird indentation in YAML-style object literals", ->
  two = (a, b) -> b
  obj = then two 1,
    1: 1
    a:
      b: ->
        fn c,
          d: e
    f: 1
  eq 1, obj[1]

test "#1274: `{} = a()` compiles to `false` instead of `a()`", ->
  a = false
  fn = -> a = true
  {} = fn()
  ok a

test "#1436: `for` etc. work as normal property names", ->
  obj = {}
  eq no, obj.hasOwnProperty 'for'
  obj.for = 'foo' of obj
  eq yes, obj.hasOwnProperty 'for'

test "#2706, Un-bracketed object as argument causes inconsistent behavior", ->
  foo = (x, y) -> y
  bar = baz: yes

  eq yes, foo x: 1, bar.baz

test "#2608, Allow inline objects in arguments to be followed by more arguments", ->
  foo = (x, y) -> y

  eq yes, foo x: 1, y: 2, yes

test "#2308, a: b = c:1", ->
  foo = a: b = c: yes
  eq b.c, yes
  eq foo.a.c, yes

test "#2317, a: b c: 1", ->
  foo = (x) -> x
  bar = a: foo c: yes
  eq bar.a.c, yes

test "#1896, a: func b, {c: d}", ->
  first = (x) -> x
  second = (x, y) -> y
  third = (x, y, z) -> z

  one = 1
  two = 2
  three = 3
  four = 4

  foo = a: second one, {c: two}
  eq foo.a.c, two

  bar = a: second one, c: two
  eq bar.a.c, two

  baz = a: second one, {c: two}, e: first first h: three
  eq baz.a.c, two

  qux = a: third one, {c: two}, e: first first h: three
  eq qux.a.e.h, three

  quux = a: third one, {c: two}, e: first(three), h: four
  eq quux.a.e, three
  eq quux.a.h, four

  corge = a: third one, {c: two}, e: second three, h: four
  eq corge.a.e.h, four

test "Implicit objects, functions and arrays", ->
  first  = (x) -> x
  second = (x, y) -> y

  foo = [
    1
    one: 1
    two: 2
    three: 3
    more:
      four: 4
      five: 5, six: 6
    2, 3, 4
    5]
  eq foo[2], 2
  eq foo[1].more.six, 6

  bar = [
    1
    first first first second 1,
      one: 1, twoandthree: twoandthree: two: 2, three: 3
      2,
    2
    one: 1
    two: 2
    three: first second ->
      no
    , ->
      3
    3
    4]
  eq bar[2], 2
  eq bar[1].twoandthree.twoandthree.two, 2
  eq bar[3].three(), 3
  eq bar[4], 3

test "#2549, Brace-less Object Literal as a Second Operand on a New Line", ->
  foo = no or
    one: 1
    two: 2
    three: 3
  eq foo.one, 1

  bar = yes and one: 1
  eq bar.one, 1

  baz = null ?
    one: 1
    two: 2
  eq baz.two, 2

test "#2757, Nested", ->
  foo =
    bar:
      one: 1,
  eq foo.bar.one, 1

  baz =
    qux:
      one: 1,
    corge:
      two: 2,
      three: three: three: 3,
    xyzzy:
      thud:
        four:
          four: 4,
      five: 5,

  eq baz.qux.one, 1
  eq baz.corge.three.three.three, 3
  eq baz.xyzzy.thud.four.four, 4
  eq baz.xyzzy.five, 5

test "#1865, syntax regression 1.1.3", ->
  foo = (x, y) -> y

  bar = a: foo (->),
    c: yes
  eq bar.a.c, yes

  baz = a: foo (->), c: yes
  eq baz.a.c, yes


test "#1322: implicit call against implicit object with block comments", ->
  ((obj, arg) ->
    eq obj.x * obj.y, 6
    ok not arg
  )
    ###
    x
    ###
    x: 2
    ### y ###
    y: 3

test "#1513: Top level bare objs need to be wrapped in parens for unary and existence ops", ->
  doesNotThrow -> CoffeeScript.run "{}?", bare: true
  doesNotThrow -> CoffeeScript.run "{}.a++", bare: true

test "#1871: Special case for IMPLICIT_END in the middle of an implicit object", ->
  result = 'result'
  ident = (x) -> x

  result = ident one: 1 if false

  eq result, 'result'

  result = ident
    one: 1
    two: 2 for i in [1..3]

  eq result.two.join(' '), '2 2 2'

test "#1871: implicit object closed by IMPLICIT_END in implicit returns", ->
  ob = do ->
    a: 1 if no
  eq ob, undefined

  # instead these return an object
  func = ->
    key:
      i for i in [1, 2, 3]

  eq func().key.join(' '), '1 2 3'

  func = ->
    key: (i for i in [1, 2, 3])

  eq func().key.join(' '), '1 2 3'

test "#1961, #1974, regression with compound assigning to an implicit object", ->

  obj = null

  obj ?=
    one: 1
    two: 2

  eq obj.two, 2

  obj = null

  obj or=
    three: 3
    four: 4

  eq obj.four, 4

test "#2207: Immediate implicit closes don't close implicit objects", ->
  func = ->
    key: for i in [1, 2, 3] then i

  eq func().key.join(' '), '1 2 3'

test "#3216: For loop declaration as a value of an implicit object", ->
  test = [0..2]
  ob =
    a: for v, i in test then i
    b: for v, i in test then i
    c: for v in test by 1 then v
    d: for v in test when true then v
  arrayEq ob.a, test
  arrayEq ob.b, test
  arrayEq ob.c, test
  arrayEq ob.d, test
  byFirstKey =
    a: for v in test by 1 then v
  arrayEq byFirstKey.a, test
  whenFirstKey =
    a: for v in test when true then v
  arrayEq whenFirstKey.a, test

test 'inline implicit object literals within multiline implicit object literals', ->
  x =
    a: aa: 0
    b: 0
  eq 0, x.b
  eq 0, x.a.aa

test "object keys with interpolations: simple cases", ->
  a = 'a'
  obj = "#{a}": yes
  eq obj.a, yes
  obj = {"#{a}": yes}
  eq obj.a, yes
  obj = {"#{a}"}
  eq obj.a, 'a'
  obj = {"#{5}"}
  eq obj[5], '5' # Note that the value is a string, just like the key.

test "object keys with interpolations: commas in implicit object", ->
  obj = "#{'a'}": 1, b: 2
  deepEqual obj, {a: 1, b: 2}
  obj = a: 1, "#{'b'}": 2
  deepEqual obj, {a: 1, b: 2}
  obj = "#{'a'}": 1, "#{'b'}": 2
  deepEqual obj, {a: 1, b: 2}

test "object keys with interpolations: commas in explicit object", ->
  obj = {"#{'a'}": 1, b: 2}
  deepEqual obj, {a: 1, b: 2}
  obj = {a: 1, "#{'b'}": 2}
  deepEqual obj, {a: 1, b: 2}
  obj = {"#{'a'}": 1, "#{'b'}": 2}
  deepEqual obj, {a: 1, b: 2}

test "object keys with interpolations: commas after key with interpolation", ->
  obj = {"#{'a'}": yes,}
  eq obj.a, yes
  obj = {
    "#{'a'}": 1,
    "#{'b'}": 2,
    ### herecomment ###
    "#{'c'}": 3,
  }
  deepEqual obj, {a: 1, b: 2, c: 3}
  obj =
    "#{'a'}": 1,
    "#{'b'}": 2,
    ### herecomment ###
    "#{'c'}": 3,
  deepEqual obj, {a: 1, b: 2, c: 3}
  obj =
    "#{'a'}": 1,
    "#{'b'}": 2,
    ### herecomment ###
    "#{'c'}": 3, "#{'d'}": 4,
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4}

test "object keys with interpolations: key with interpolation mixed with `@prop`", ->
  deepEqual (-> {@a, "#{'b'}": 2}).call(a: 1), {a: 1, b: 2}

test "object keys with interpolations: evaluate only once", ->
  count = 0
  a = -> count++; 'a'
  obj = {"#{a()}"}
  eq obj.a, 'a'
  eq count, 1

test "object keys with interpolations: evaluation order", ->
  arr = []
  obj =
    a: arr.push 1
    b: arr.push 2
    "#{'c'}": arr.push 3
    "#{'d'}": arr.push 4
    e: arr.push 5
    "#{'f'}": arr.push 6
    g: arr.push 7
  arrayEq arr, [1..7]
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7}

test "object keys with interpolations: object starting with dynamic key", ->
  obj =
    "#{'a'}": 1
    b: 2
  deepEqual obj, {a: 1, b: 2}

test "object keys with interpolations: comments in implicit object", ->
  obj =
    ### leading comment ###
    "#{'a'}": 1

    ### middle ###

    "#{'b'}": 2
    # regular comment
    'c': 3
    ### foo ###
    d: 4
    "#{'e'}": 5
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5}

  # Comments in explicit object.
  obj = {
    ### leading comment ###
    "#{'a'}": 1

    ### middle ###

    "#{'b'}": 2
    # regular comment
    'c': 3
    ### foo ###
    d: 4
    "#{'e'}": 5
  }
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5}

test "object keys with interpolations: more complicated case", ->
  obj = {
    "#{'interpolated'}":
      """
        #{ '''nested''' }
      """: 123: 456
  }
  deepEqual obj,
    interpolated:
      nested:
        123: 456

test "#4324: Shorthand after interpolated key", ->
  a = 2
  obj = {"#{1}": 1, a}
  eq 1, obj[1]
  eq 2, obj.a

test "computed property keys: simple cases", ->
  a = 'a'
  obj = [a]: yes
  eq obj.a, yes
  obj = {[a]: yes}
  eq obj.a, yes
  obj = {[a]}
  eq obj.a, 'a'
  obj = {[5]}
  eq obj[5], 5
  obj = {['5']}
  eq obj['5'], '5'

test "computed property keys: commas in implicit object", ->
  obj = ['a']: 1, b: 2
  deepEqual obj, {a: 1, b: 2}
  obj = a: 1, ['b']: 2
  deepEqual obj, {a: 1, b: 2}
  obj = ['a']: 1, ['b']: 2
  deepEqual obj, {a: 1, b: 2}

test "computed property keys: commas in explicit object", ->
  obj = {['a']: 1, b: 2}
  deepEqual obj, {a: 1, b: 2}
  obj = {a: 1, ['b']: 2}
  deepEqual obj, {a: 1, b: 2}
  obj = {['a']: 1, ['b']: 2}
  deepEqual obj, {a: 1, b: 2}

test "computed property keys: commas after key with interpolation", ->
  obj = {['a']: yes,}
  eq obj.a, yes
  obj = {
    ['a']: 1,
    ['b']: 2,
    ### herecomment ###
    ['c']: 3,
  }
  deepEqual obj, {a: 1, b: 2, c: 3}
  obj =
    ['a']: 1,
    ['b']: 2,
    ### herecomment ###
    ['c']: 3,
  deepEqual obj, {a: 1, b: 2, c: 3}
  obj =
    ['a']: 1,
    ['b']: 2,
    ### herecomment ###
    ['c']: 3, ['d']: 4,
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4}

test "computed property keys: key with interpolation mixed with `@prop`", ->
  deepEqual (-> {@a, ['b']: 2}).call(a: 1), {a: 1, b: 2}

test "computed property keys: evaluate only once", ->
  count = 0
  a = -> count++; 'a'
  obj = {[a()]}
  eq obj.a, 'a'
  eq count, 1

test "computed property keys: evaluation order", ->
  arr = []
  obj =
    a: arr.push 1
    b: arr.push 2
    ['c']: arr.push 3
    ['d']: arr.push 4
    e: arr.push 5
    ['f']: arr.push 6
    g: arr.push 7
  arrayEq arr, [1..7]
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7}

test "computed property keys: object starting with dynamic key", ->
  obj =
    ['a']: 1
    b: 2
  deepEqual obj, {a: 1, b: 2}

test "computed property keys: comments in implicit object", ->
  obj =
    ### leading comment ###
    ['a']: 1

    ### middle ###

    ['b']: 2
    # regular comment
    'c': 3
    ### foo ###
    d: 4
    ['e']: 5
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5}

  obj = {
    ### leading comment ###
    ['a']: 1

    ### middle ###

    ['b']: 2
    # regular comment
    'c': 3
    ### foo ###
    d: 4
    ['e']: 5
  }
  deepEqual obj, {a: 1, b: 2, c: 3, d: 4, e: 5}

test "computed property keys: more complicated case", ->
  obj = {
    ['interpolated']:
       ['nested']:
         123: 456
  }
  deepEqual obj,
    interpolated:
      nested:
        123: 456

test "computed property keys: empty array as key", ->
  o1 = { [[]] }
  deepEqual o1, { [[]]: [] }
  arrayEq o1[[]], []
  o2 = { [[]]: 1 }
  deepEqual o2, { [[]]: 1 }
  eq o2[[]], 1
  o3 = [[]]: 1
  deepEqual o3, { [[]]: 1 }
  deepEqual o3, { [[]]: 1 }
  eq o3[[]], 1
  o4 = a: 1, [[]]: 2
  deepEqual o4, { a: 1, [[]]: 2 }
  eq o4.a, 1,
  eq o4[[]], 2
  o5 = { a: 1, [[]]: 2 }
  deepEqual o5, { a: 1, [[]]: 2 }
  eq o5.a, 1,
  eq o5[[]], 2

test "computed property keys: shorthand after computed property key", ->
  a = 2
  obj = {[1]: 1, a}
  eq 1, obj[1]
  eq 2, obj.a

test "computed property keys: shorthand computed property key", ->
  a = 'b'
  o = {[a]}
  p = {a}
  r = {['a']}
  eq o.b, 'b'
  eq p.a, o.b
  eq r.a, 'a'

  foo = -> "a"
  obj = { [foo()] }
  eq obj.a, 'a'

test "computed property keys: arrays", ->
  b = 'b'
  f = (c) -> "#{c}1"
  obj =
    ['a']: [1, 2, 3]
    [b]: [4, 5, 6]
    [f(b)]: [7, 8, 9]
  arrayEq obj.a, [1, 2, 3]
  arrayEq obj.b, [4, 5, 6]
  arrayEq obj.b1, [7, 8, 9]

test "computed property keys: examples from developer.mozilla.org (Object initializer)", ->
  i = 0
  obj =
    ['foo' + ++i]: i
    ['foo' + ++i]: i
    ['foo' + ++i]: i
  eq obj.foo1, 1
  eq obj.foo2, 2
  eq obj.foo3, 3

  param = 'size'
  config =
    [param]: 12,
    ['mobile' + param.charAt(0).toUpperCase() + param.slice(1)]: 4
  deepEqual config, {size: 12,  mobileSize: 4}

test "computed property keys: [Symbol.iterator]", ->
  obj =
    [Symbol.iterator]: ->
      yield "hello"
      yield "world"
  arrayEq [obj...], ['hello', 'world']

test "computed property keys: Class property", ->
  increment_method = "increment"
  decrement_method = "decrement"
  class Obs
    constructor: (@count) ->
    [increment_method]: -> @count += 1
    [decrement_method]: -> @count -= 1
  ob = new Obs 2
  eq ob.increment(), 3
  eq ob.decrement(), 2

test "#1263: Braceless object return", ->
  fn = ->
    return
      a: 1
      b: 2
      c: -> 3

  obj = fn()
  eq 1, obj.a
  eq 2, obj.b
  eq 3, obj.c()

test "#4564: indent should close implicit object", ->
  f = (x) -> x

  arrayEq ['a'],
    for key of f a: 1
      key

  g = null
  if f a: 1
    g = 3
  eq g, 3

  h = null
  if a: (i for i in [1, 2, 3])
    h = 4
  eq h, 4

test "#4544: Postfix conditionals in first line of implicit object literals", ->
  two =
    foo:
      bar: 42 if yes
      baz: 1337
  eq 42, two.foo.bar
  eq 1337, two.foo.baz

  f = (x) -> x

  three =
    foo: f
      bar: 42 if yes
      baz: 1337
  eq 42, three.foo.bar
  eq 1337, three.foo.baz

  four =
    f
      foo:
        bar: 42 if yes
      baz: 1337
  eq 42, four.foo.bar
  eq 1337, four.baz

  x = bar: 42 if no
  baz: 1337
  ok not x?

  # Example from #2051
  a = null
  _alert = (arg) -> a = arg
  _alert
    val3: "works" if true
    val: "hello"
    val2: "all good"
  eq a.val2, "all good"

test "#4579: Postfix for/while/until in first line of implicit object literals", ->
  two =
    foo:
      bar1: x for x in [1, 2, 3]
      bar2: x + y for x, y in [1, 2, 3]
      baz: 1337
  arrayEq [1, 2, 3], two.foo.bar1
  arrayEq [1, 3, 5], two.foo.bar2
  eq 1337, two.foo.baz

  f = (x) -> x

  three =
    foo: f
      bar1: x + y for x, y of a: 'b', c: 'd'
      bar2: x + 'c' for x of a: 1, b: 2
      baz: 1337
  arrayEq ['ab', 'cd'], three.foo.bar1
  arrayEq ['ac', 'bc'], three.foo.bar2
  eq 1337, three.foo.baz

  four =
    f
      foo:
        "bar_#{x}": x for x of a: 1, b: 2
      baz: 1337
  eq 'a', four.foo[0].bar_a
  eq 'b', four.foo[1].bar_b
  eq 1337, four.baz

  x = bar: 42 for y in [1]
  baz: 1337
  eq x.bar, 42

  i = 5
  five =
    foo:
      bar: i while i-- > 0
      baz: 1337
  arrayEq [4, 3, 2, 1, 0], five.foo.bar
  eq 1337, five.foo.baz

  i = 5
  six =
    foo:
      bar: i until i-- <= 0
      baz: 1337
  arrayEq [4, 3, 2, 1, 0], six.foo.bar
  eq 1337, six.foo.baz

test "#5204: not parsed as static property", ->
  doesNotThrowCompileError "@ [b]: 2"

test "#5292: implicit object after line continuer in implicit object property value", ->
  a =
    b: 0 or
      c: 1
  eq 1, a.b.c

  # following object property
  a =
    b: null ?
      c: 1
    d: 2
  eq 1, a.b.c
  eq 2, a.d

  # multiline nested object
  a =
    b: 0 or
      c: 1
      d: 2
    e: 3
  eq 1, a.b.c
  eq 2, a.b.d
  eq 3, a.e

test "#5368: continuing object and array literals", ->
  { a
    b: { c }
  } = {a: 1, b: {c: 2}}
  eq a, 1
  eq c, 2

  [d
   e: f
  ] = [3, {e: 4}]
  eq d, 3
  eq f, 4
  A =
    [d
     e: f
    ]
  eq A[0], 3
  eq A[1].e, 4

  for obj in [
    {a: a
     c: c
    }
    {a: a
     c
    }
    {a
     c: c
    }
    {a
     c
    }
  ]
    eq obj.a, 1
    eq obj.c, 2

</script>
<script type="text/x-coffeescript" class="test" id="operators">
# Operators
# ---------

# * Operators
# * Existential Operator (Binary)
# * Existential Operator (Unary)
# * Aliased Operators
# * [not] in/of
# * Chained Comparison

test "binary (2-ary) math operators do not require spaces", ->
  a = 1
  b = -1
  eq +1, a*-b
  eq -1, a*+b
  eq +1, a/-b
  eq -1, a/+b

test "operators should respect new lines as spaced", ->
  a = 123 +
  456
  eq 579, a

  b = "1#{2}3" +
  "456"
  eq '123456', b

test "multiple operators should space themselves", ->
  eq (+ +1), (- -1)

test "compound operators on successive lines", ->
  a = 1
  a +=
  1
  eq a, 2

test "bitwise operators", ->
  eq  2, (10 &   3)
  eq 11, (10 |   3)
  eq  9, (10 ^   3)
  eq 80, (10 <<  3)
  eq  1, (10 >>  3)
  eq  1, (10 >>> 3)
  num = 10; eq  2, (num &=   3)
  num = 10; eq 11, (num |=   3)
  num = 10; eq  9, (num ^=   3)
  num = 10; eq 80, (num <<=  3)
  num = 10; eq  1, (num >>=  3)
  num = 10; eq  1, (num >>>= 3)

test "`instanceof`", ->
  ok new String instanceof String
  ok new Boolean instanceof Boolean
  # `instanceof` supports negation by prefixing the operator with `not`
  ok new Number not instanceof String
  ok new Array not instanceof Boolean

test "use `::` operator on keywords `this` and `@`", ->
  nonce = {}
  obj =
    withAt:   -> @::prop
    withThis: -> this::prop
  obj.prototype = prop: nonce
  eq nonce, obj.withAt()
  eq nonce, obj.withThis()


# Existential Operator (Binary)

test "binary existential operator", ->
  nonce = {}

  b = a ? nonce
  eq nonce, b

  a = null
  b = undefined
  b = a ? nonce
  eq nonce, b

  a = false
  b = a ? nonce
  eq false, b

  a = 0
  b = a ? nonce
  eq 0, b

test "binary existential operator conditionally evaluates second operand", ->
  i = 1
  func = -> i -= 1
  result = func() ? func()
  eq result, 0

test "binary existential operator with negative number", ->
  a = null ? - 1
  eq -1, a


# Existential Operator (Unary)

test "postfix existential operator", ->
  ok (if nonexistent? then false else true)
  defined = true
  ok defined?
  defined = false
  ok defined?

test "postfix existential operator only evaluates its operand once", ->
  semaphore = 0
  fn = ->
    ok false if semaphore
    ++semaphore
  ok(if fn()? then true else false)

test "negated postfix existential operator", ->
  ok !nothing?.value

test "postfix existential operator on expressions", ->
  eq true, (1 or 0)?, true


# `is`,`isnt`,`==`,`!=`

test "`==` and `is` should be interchangeable", ->
  a = b = 1
  ok a is 1 and b == 1
  ok a == b
  ok a is b

test "`!=` and `isnt` should be interchangeable", ->
  a = 0
  b = 1
  ok a isnt 1 and b != 0
  ok a != b
  ok a isnt b


# [not] in/of

# - `in` should check if an array contains a value using `indexOf`
# - `of` should check if a property is defined on an object using `in`
test "in, of", ->
  arr = [1]
  ok 0 of arr
  ok 1 in arr
  # prefixing `not` to `in and `of` should negate them
  ok 1 not of arr
  ok 0 not in arr

test "`in` should be able to operate on an array literal", ->
  ok 2 in [0, 1, 2, 3]
  ok 4 not in [0, 1, 2, 3]
  arr = [0, 1, 2, 3]
  ok 2 in arr
  ok 4 not in arr
  # should cache the value used to test the array
  arr = [0]
  val = 0
  ok val++ in arr
  ok val++ not in arr
  val = 0
  ok val++ of arr
  ok val++ not of arr

test "`of` and `in` should be able to operate on instance variables", ->
  obj = {
    list: [2,3]
    in_list: (value) -> value in @list
    not_in_list: (value) -> value not in @list
    of_list: (value) -> value of @list
    not_of_list: (value) -> value not of @list
  }
  ok obj.in_list 3
  ok obj.not_in_list 1
  ok obj.of_list 0
  ok obj.not_of_list 2

test "#???: `in` with cache and `__indexOf` should work in argument lists", ->
  eq 1, [Object() in Array()].length

test "#737: `in` should have higher precedence than logical operators", ->
  eq 1, 1 in [1] and 1

test "#768: `in` should preserve evaluation order", ->
  share = 0
  a = -> share++ if share is 0
  b = -> share++ if share is 1
  c = -> share++ if share is 2
  ok a() not in [b(),c()]
  eq 3, share

test "#1099: empty array after `in` should compile to `false`", ->
  eq 1, [5 in []].length
  eq false, do -> return 0 in []

test "#1354: optimized `in` checks should not happen when splats are present", ->
  a = [6, 9]
  eq 9 in [3, a...], true

test "#1100: precedence in or-test compilation of `in`", ->
  ok 0 in [1 and 0]
  ok 0 in [1, 1 and 0]
  ok not (0 in [1, 0 or 1])

test "#1630: `in` should check `hasOwnProperty`", ->
  ok undefined not in length: 1

test "#1714: lexer bug with raw range `for` followed by `in`", ->
  0 for [1..2]
  ok not ('a' in ['b'])

  0 for [1..2]; ok not ('a' in ['b'])

  0 for [1..10] # comment ending
  ok not ('a' in ['b'])

  # lexer state (specifically @seenFor) should be reset before each compilation
  CoffeeScript.compile "0 for [1..2]"
  CoffeeScript.compile "'a' in ['b']"

test "#1099: statically determined `not in []` reporting incorrect result", ->
  ok 0 not in []

test "#1099: make sure expression tested gets evaluted when array is empty", ->
  a = 0
  (do -> a = 1) in []
  eq a, 1

# Chained Comparison

test "chainable operators", ->
  ok 100 > 10 > 1 > 0 > -1
  ok -1 < 0 < 1 < 10 < 100

test "`is` and `isnt` may be chained", ->
  ok true is not false is true is not false
  ok 0 is 0 isnt 1 is 1

test "different comparison operators (`>`,`<`,`is`,etc.) may be combined", ->
  ok 1 < 2 > 1
  ok 10 < 20 > 2+3 is 5

test "some chainable operators can be negated by `unless`", ->
  ok (true unless 0==10!=100)

test "operator precedence: `|` lower than `<`", ->
  eq 1, 1 | 2 < 3 < 4

test "preserve references", ->
  a = b = c = 1
  # `a == b <= c` should become `a === b && b <= c`
  # (this test does not seem to test for this)
  ok a == b <= c

test "chained operations should evaluate each value only once", ->
  a = 0
  ok 1 > a++ < 1

test "#891: incorrect inversion of chained comparisons", ->
  ok (true unless 0 > 1 > 2)
  ok (true unless (this.NaN = 0/0) < 0/0 < this.NaN)

test "#1234: Applying a splat to :: applies the splat to the wrong object", ->
  nonce = {}
  class C
    method: -> @nonce
    nonce: nonce

  arr = []
  eq nonce, C::method arr... # should be applied to `C::`

test "#1102: String literal prevents line continuation", ->
  eq "': '", '' +
     "': '"

test "#1703, ---x is invalid JS", ->
  x = 2
  eq (- --x), -1

test "Regression with implicit calls against an indented assignment", ->
  eq 1, a =
    1

  eq a, 1

test "#2155 ... conditional assignment to a closure", ->
  x = null
  func = -> x ?= (-> if true then 'hi')
  func()
  eq x(), 'hi'

test "#2197: Existential existential double trouble", ->
  counter = 0
  func = -> counter++
  func()? ? 100
  eq counter, 1

test "#2567: Optimization of negated existential produces correct result", ->
  a = 1
  ok !(!a?)
  ok !b?

test "#2508: Existential access of the prototype", ->
  eq NonExistent?::nothing, undefined
  eq(
    NonExistent
    ?::nothing
    undefined
  )
  ok Object?::toString
  ok(
    Object
    ?::toString
  )

test "floor division operator", ->
  eq 2, 7 // 3
  eq -3, -7 // 3
  eq NaN, 0 // 0

test "floor division operator compound assignment", ->
  a = 7
  a //= 1 + 1
  eq 3, a

test "modulo operator", ->
  check = (a, b, expected) ->
    eq expected, a %% b, "expected #{a} %%%% #{b} to be #{expected}"
  check 0, 1, 0
  check 0, -1, -0
  check 1, 0, NaN
  check 1, 2, 1
  check 1, -2, -1
  check 1, 3, 1
  check 2, 3, 2
  check 3, 3, 0
  check 4, 3, 1
  check -1, 3, 2
  check -2, 3, 1
  check -3, 3, 0
  check -4, 3, 2
  check 5.5, 2.5, 0.5
  check -5.5, 2.5, 2.0

test "modulo operator compound assignment", ->
  a = -2
  a %%= 5
  eq 3, a

test "modulo operator converts arguments to numbers", ->
  eq 1, 1 %% '42'
  eq 1, '1' %% 42
  eq 1, '1' %% '42'

test "#3361: Modulo operator coerces right operand once", ->
  count = 0
  res = 42 %% valueOf: -> count += 1
  eq 1, count
  eq 0, res

test "#3363: Modulo operator coercing order", ->
  count = 2
  a = valueOf: -> count *= 2
  b = valueOf: -> count += 1
  eq 4, a %% b
  eq 5, count

test "#3598: Unary + and - coerce the operand once when it is an identifier", ->
  # Unary + and - do not generate `_ref`s when the operand is a number, for
  # readability. To make sure that they do when the operand is an identifier,
  # test that they are consistent with another unary operator as well as another
  # complex expression.
  # Tip: Making one of the tests temporarily fail lets you easily inspect the
  # compiled JavaScript.

  assertOneCoercion = (fn) ->
    count = 0
    value = valueOf: -> count++; 1
    fn value
    eq 1, count

  eq 1, 1 ? 0
  eq 1, +1 ? 0
  eq -1, -1 ? 0
  assertOneCoercion (a) ->
    eq 1, +a ? 0
  assertOneCoercion (a) ->
    eq -1, -a ? 0
  assertOneCoercion (a) ->
    eq -2, ~a ? 0
  assertOneCoercion (a) ->
    eq 0.5, a / 2 ? 0

  ok -2 <= 1 < 2
  ok -2 <= +1 < 2
  ok -2 <= -1 < 2
  assertOneCoercion (a) ->
    ok -2 <= +a < 2
  assertOneCoercion (a) ->
    ok -2 <= -a < 2
  assertOneCoercion (a) ->
    ok -2 <= ~a < 2
  assertOneCoercion (a) ->
    ok -2 <= a / 2 < 2

  arrayEq [0], (n for n in [0] by 1)
  arrayEq [0], (n for n in [0] by +1)
  arrayEq [0], (n for n in [0] by -1)
  assertOneCoercion (a) ->
    arrayEq [0], (n for n in [0] by +a)
  assertOneCoercion (a) ->
    arrayEq [0], (n for n in [0] by -a)
  assertOneCoercion (a) ->
    arrayEq [0], (n for n in [0] by ~a)
  assertOneCoercion (a) ->
    arrayEq [0], (n for n in [0] by a * 2 / 2)

  ok 1 in [0, 1]
  ok +1 in [0, 1]
  ok -1 in [0, -1]
  assertOneCoercion (a) ->
    ok +a in [0, 1]
  assertOneCoercion (a) ->
    ok -a in [0, -1]
  assertOneCoercion (a) ->
    ok ~a in [0, -2]
  assertOneCoercion (a) ->
    ok a / 2 in [0, 0.5]

test "'new' target", ->
  nonce = {}
  ctor  = -> nonce

  eq (new ctor), nonce
  eq (new ctor()), nonce

  ok new class

  ctor  = class
  ok (new ctor) instanceof ctor
  ok (new ctor()) instanceof ctor

  # Force an executable class body
  ctor  = class then a = 1
  ok (new ctor) instanceof ctor

  get   = -> ctor
  ok (new get()) not instanceof ctor
  ok (new (get())()) instanceof ctor

  # classes must be called with `new`. In this case `new` applies to `get` only
  throws -> new get()()

</script>
<script type="text/x-coffeescript" class="test" id="option_parser">
# Option Parser
# -------------

# Ensure that the OptionParser handles arguments correctly.
return unless require?
{OptionParser} = require './../lib/coffeescript/optparse'

flags = [
  ['-r', '--required [DIR]',  'desc required']
  ['-o', '--optional',        'desc optional']
  ['-l', '--list [FILES*]',   'desc list']
]

banner = '''
  banner text
'''

opt = new OptionParser flags, banner

test "basic arguments", ->
  args = ['one', 'two', 'three', '-r', 'dir']
  result = opt.parse args
  arrayEq args, result.arguments
  eq undefined, result.required

test "boolean and parameterised options", ->
  result = opt.parse ['--optional', '-r', 'folder', 'one', 'two']
  ok result.optional
  eq 'folder', result.required
  arrayEq ['one', 'two'], result.arguments

test "list options", ->
  result = opt.parse ['-l', 'one.txt', '-l', 'two.txt', 'three']
  arrayEq ['one.txt', 'two.txt'], result.list
  arrayEq ['three'], result.arguments

test "-- and interesting combinations", ->
  result = opt.parse ['-o','-r','a','-r','b','-o','--','-a','b','--c','d']
  arrayEq ['-a', 'b', '--c', 'd'], result.arguments
  ok result.optional
  eq 'b', result.required

  args = ['--','-o','a','-r','c','-o','--','-a','arg0','-b','arg1']
  result = opt.parse args
  eq undefined, result.optional
  eq undefined, result.required
  arrayEq args[1..], result.arguments

test "throw if multiple flags try to use the same short or long name", ->
  throws -> new OptionParser [
    ['-r', '--required [DIR]', 'required']
    ['-r', '--long',           'switch']
  ]

  throws -> new OptionParser [
    ['-a', '--append [STR]', 'append']
    ['-b', '--append',       'append with -b short opt']
  ]

  throws -> new OptionParser [
    ['--just-long', 'desc']
    ['--just-long', 'another desc']
  ]

  throws -> new OptionParser [
    ['-j', '--just-long', 'desc']
    ['--just-long', 'another desc']
  ]

  throws -> new OptionParser [
    ['--just-long',       'desc']
    ['-j', '--just-long', 'another desc']
  ]

test "outputs expected help text", ->
  expectedBanner = '''

banner text

  -r, --required     desc required
  -o, --optional     desc optional
  -l, --list         desc list

  '''
  ok opt.help() is expectedBanner

  expected = [
    ''
    '  -r, --required     desc required'
    '  -o, --optional     desc optional'
    '  -l, --list         desc list'
    ''
  ].join('\n')
  ok new OptionParser(flags).help() is expected

</script>
<script type="text/x-coffeescript" class="test" id="package">
return if window? or testingBrowser?

{EventEmitter}  = require 'events'
{join}          = require 'path'
{cwd}           = require 'process'
{pathToFileURL} = require 'url'


packageEntryPath = join cwd(), 'lib/coffeescript/index.js'
packageEntryUrl  = pathToFileURL packageEntryPath


test "the coffeescript package exposes all members as named exports in Node", ->

  requiredPackage = require packageEntryPath
  requiredKeys = Object.keys requiredPackage

  importedPackage = await import(packageEntryUrl)
  importedKeys = new Set Object.keys(importedPackage)

  # In `command.coffee`, CoffeeScript extends a `new EventEmitter`;
  # we want to ignore these additional added keys.
  eventEmitterKeys = new Set Object.getOwnPropertyNames(Object.getPrototypeOf(new EventEmitter))

  for key in requiredKeys when not eventEmitterKeys.has(key)
    # Use `eq` test so that missing keys have their names printed in the error message.
    eq (if importedKeys.has(key) then key else undefined), key

</script>
<script type="text/x-coffeescript" class="test" id="parser">
# Parser
# ---------

test "operator precedence for logical operators", ->
  source = '''
    a or b and c
  '''
  {body: block} = CoffeeScript.nodes source
  [expression] = block.expressions
  eq expression.first.base.value, 'a'
  eq expression.operator, '||'
  eq expression.second.first.base.value, 'b'
  eq expression.second.operator, '&&'
  eq expression.second.second.base.value, 'c'

test "operator precedence for bitwise operators", ->
  source = '''
    a | b ^ c & d
  '''
  {body: block} = CoffeeScript.nodes source
  [expression] = block.expressions
  eq expression.first.base.value, 'a'
  eq expression.operator, '|'
  eq expression.second.first.base.value, 'b'
  eq expression.second.operator, '^'
  eq expression.second.second.first.base.value, 'c'
  eq expression.second.second.operator, '&'
  eq expression.second.second.second.base.value, 'd'

test "operator precedence for binary ? operator", ->
  source = '''
     a ? b and c
  '''
  {body: block} = CoffeeScript.nodes source
  [expression] = block.expressions
  eq expression.first.base.value, 'a'
  eq expression.operator, '?'
  eq expression.second.first.base.value, 'b'
  eq expression.second.operator, '&&'
  eq expression.second.second.base.value, 'c'

test "new calls have a range including the new", ->
  source = '''
    a = new B().c(d)
  '''
  {body: block} = CoffeeScript.nodes source

  assertColumnRange = (node, firstColumn, lastColumn) ->
    eq node.locationData.first_line, 0
    eq node.locationData.first_column, firstColumn
    eq node.locationData.last_line, 0
    eq node.locationData.last_column, lastColumn

  [assign] = block.expressions
  outerCall = assign.value.base
  innerValue = outerCall.variable
  innerCall = innerValue.base

  assertColumnRange assign, 0, 15
  assertColumnRange outerCall, 4, 15
  assertColumnRange innerValue, 4, 12
  assertColumnRange innerCall, 4, 10

test "location data is properly set for nested `new`", ->
  source = '''
    new new A()()
  '''
  {body: block} = CoffeeScript.nodes source

  assertColumnRange = (node, firstColumn, lastColumn) ->
    eq node.locationData.first_line, 0
    eq node.locationData.first_column, firstColumn
    eq node.locationData.last_line, 0
    eq node.locationData.last_column, lastColumn

  [{base: outerCall}] = block.expressions
  innerCall = outerCall.variable

  assertColumnRange outerCall, 0, 12
  assertColumnRange innerCall, 4, 10

</script>
<script type="text/x-coffeescript" class="test" id="ranges">
# Range Literals
# --------------

# TODO: add indexing and method invocation tests: [1..4][0] is 1, [0...3].toString()

# shared array
shared = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

test "basic inclusive ranges", ->
  arrayEq [1, 2, 3] , [1..3]
  arrayEq [0, 1, 2] , [0..2]
  arrayEq [0, 1]    , [0..1]
  arrayEq [0]       , [0..0]
  arrayEq [-1]      , [-1..-1]
  arrayEq [-1, 0]   , [-1..0]
  arrayEq [-1, 0, 1], [-1..1]

test "basic exclusive ranges", ->
  arrayEq [1, 2, 3] , [1...4]
  arrayEq [0, 1, 2] , [0...3]
  arrayEq [0, 1]    , [0...2]
  arrayEq [0]       , [0...1]
  arrayEq [-1]      , [-1...0]
  arrayEq [-1, 0]   , [-1...1]
  arrayEq [-1, 0, 1], [-1...2]

  arrayEq [], [1...1]
  arrayEq [], [0...0]
  arrayEq [], [-1...-1]

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  arrayEq [1, 2, 3] , [1 ... 4]
  arrayEq [0, 1, 2] , [0 ... 3]
  arrayEq [0, 1]    , [0 ... 2]
  arrayEq [0]       , [0 ... 1]
  arrayEq [-1]      , [-1 ... 0]
  arrayEq [-1, 0]   , [-1 ... 1]
  arrayEq [-1, 0, 1], [-1 ... 2]

  arrayEq [], [1 ... 1]
  arrayEq [], [0 ... 0]
  arrayEq [], [-1 ... -1]

test "downward ranges", ->
  arrayEq shared, [9..0].reverse()
  arrayEq [5, 4, 3, 2] , [5..2]
  arrayEq [2, 1, 0, -1], [2..-1]

  arrayEq [3, 2, 1]  , [3..1]
  arrayEq [2, 1, 0]  , [2..0]
  arrayEq [1, 0]     , [1..0]
  arrayEq [0]        , [0..0]
  arrayEq [-1]       , [-1..-1]
  arrayEq [0, -1]    , [0..-1]
  arrayEq [1, 0, -1] , [1..-1]
  arrayEq [0, -1, -2], [0..-2]

  arrayEq [4, 3, 2], [4...1]
  arrayEq [3, 2, 1], [3...0]
  arrayEq [2, 1]   , [2...0]
  arrayEq [1]      , [1...0]
  arrayEq []       , [0...0]
  arrayEq []       , [-1...-1]
  arrayEq [0]      , [0...-1]
  arrayEq [0, -1]  , [0...-2]
  arrayEq [1, 0]   , [1...-1]
  arrayEq [2, 1, 0], [2...-1]

test "ranges with variables as enpoints", ->
  [a, b] = [1, 3]
  arrayEq [1, 2, 3], [a..b]
  arrayEq [1, 2]   , [a...b]
  b = -2
  arrayEq [1, 0, -1, -2], [a..b]
  arrayEq [1, 0, -1]    , [a...b]

test "ranges with expressions as endpoints", ->
  [a, b] = [1, 3]
  arrayEq [2, 3, 4, 5, 6], [(a+1)..2*b]
  arrayEq [2, 3, 4, 5]   , [(a+1)...2*b]

  # Should not trigger implicit call, e.g. rest ... => rest(...)
  arrayEq [2, 3, 4, 5]   , [(a+1) ... 2*b]

test "large ranges are generated with looping constructs", ->
  down = [99..0]
  eq 100, (len = down.length)
  eq   0, down[len - 1]

  up = [0...100]
  eq 100, (len = up.length)
  eq  99, up[len - 1]

test "for-from loops over ranges", ->
  array1 = []
  for x from [20..30]
    array1.push(x)
    break if x is 25
  arrayEq array1, [20, 21, 22, 23, 24, 25]

test "for-from comprehensions over ranges", ->
  array1 = (x + 10 for x from [20..25])
  ok array1.join(' ') is '30 31 32 33 34 35'

  array2 = (x for x from [20..30] when x %% 2 == 0)
  ok array2.join(' ') is '20 22 24 26 28 30'

test "#1012 slices with arguments object", ->
  expected = [0..9]
  argsAtStart = (-> [arguments[0]..9]) 0
  arrayEq expected, argsAtStart
  argsAtEnd = (-> [0..arguments[0]]) 9
  arrayEq expected, argsAtEnd
  argsAtBoth = (-> [arguments[0]..arguments[1]]) 0, 9
  arrayEq expected, argsAtBoth

test "#1409: creating large ranges outside of a function body", ->
  CoffeeScript.eval '[0..100]'

test "#2047: Infinite loop possible when `for` loop with `range` uses variables", ->
  up = 1
  down = -1
  a = 1
  b = 5

  testRange = (arg) ->
    [from, to, step, expectedResult] = arg
    r = (x for x in [from..to] by step)
    arrayEq r, expectedResult

  testData = [
    [1, 5, 1, [1..5]]
    [1, 5, -1, []]
    [1, 5, up, [1..5]]
    [1, 5, down, []]

    [a, 5, 1, [1..5]]
    [a, 5, -1, []]
    [a, 5, up, [1..5]]
    [a, 5, down, []]

    [1, b, 1, [1..5]]
    [1, b, -1, []]
    [1, b, up, [1..5]]
    [1, b, down, []]

    [a, b, 1, [1..5]]
    [a, b, -1, []]
    [a, b, up, [1..5]]
    [a, b, down, []]

    [5, 1, 1, []]
    [5, 1, -1, [5..1]]
    [5, 1, up, []]
    [5, 1, down,  [5..1]]

    [5, a, 1, []]
    [5, a, -1, [5..1]]
    [5, a, up, []]
    [5, a, down, [5..1]]

    [b, 1, 1, []]
    [b, 1, -1, [5..1]]
    [b, 1, up, []]
    [b, 1, down, [5..1]]

    [b, a, 1, []]
    [b, a, -1, [5..1]]
    [b, a, up, []]
    [b, a, down, [5..1]]
  ]

  testRange d for d in testData

test "#2047: from, to and step as variables", ->
  up = 1
  down = -1
  a = 1
  b = 5

  r = (x for x in [a..b] by up)
  arrayEq r, [1..5]

  r = (x for x in [a..b] by down)
  arrayEq r, []

  r = (x for x in [b..a] by up)
  arrayEq r, []

  r = (x for x in [b..a] by down)
  arrayEq r, [5..1]

  a = 1
  b = -1
  step = 0
  r = (x for x in [b..a] by step)
  arrayEq r, []

test "#4884: Range not declaring var for the 'i'", ->
  'use strict'
  [0..21].forEach (idx) ->
    idx + 1

  eq global.i, undefined

test "#4889: `for` loop unexpected behavior", ->
  n = 1
  result = []
  for i in [0..n]
    result.push i
    for j in [(i+1)..n]
      result.push j

  arrayEq result, [0,1,1,2,1]

test "#4889: `for` loop unexpected behavior with `by 1` on second loop", ->
  n = 1
  result = []
  for i in [0..n]
    result.push i
    for j in [(i+1)..n] by 1
      result.push j

  arrayEq result, [0,1,1]

test "countdown example from docs", ->
  countdown = (num for num in [10..1])
  arrayEq countdown, [10,9,8,7,6,5,4,3,2,1]

test "counting up when the range goes down returns an empty array", ->
  countdown = (num for num in [10..1] by 1)
  arrayEq countdown, []

test "counting down when the range goes up returns an empty array", ->
  countup = (num for num in [1..10] by -1)
  arrayEq countup, []

test "counting down by too much returns just the first value", ->
  countdown = (num for num in [10..1] by -100)
  arrayEq countdown, [10]

test "counting up by too much returns just the first value", ->
  countup = (num for num in [1..10] by 100)
  arrayEq countup, [1]

</script>
<script type="text/x-coffeescript" class="test" id="regex">
# Regular Expression Literals
# ---------------------------

# TODO: add method invocation tests: /regex/.toString()

# * Regexen
# * Heregexen

test "basic regular expression literals", ->
  ok 'a'.match(/a/)
  ok 'a'.match /a/
  ok 'a'.match(/a/g)
  ok 'a'.match /a/g

test "division is not confused for a regular expression", ->
  # Any spacing around the slash is allowed when it cannot be a regex.
  eq 2, 4 / 2 / 1
  eq 2, 4/2/1
  eq 2, 4/ 2 / 1
  eq 2, 4 /2 / 1
  eq 2, 4 / 2/ 1
  eq 2, 4 / 2 /1
  eq 2, 4 /2/ 1

  a = (regex) -> regex.test 'a b c'
  a.valueOf = -> 4
  b = 2
  g = 1

  eq 2, a / b/g
  eq 2, a/ b/g
  eq 2, a / b/ g
  eq 2, a	/	b/g # Tabs.
  eq 2, a / b/g # Non-breaking spaces.
  eq true, a /b/g
  # Use parentheses to disambiguate.
  eq true, a(/ b/g)
  eq true, a(/ b/)
  eq true, a (/ b/)
  # Escape to disambiguate.
  eq true, a /\ b/g
  eq false, a	/\	b/g
  eq true, a /\ b/

  obj = method: -> 2
  two = 2
  eq 2, (obj.method()/two + obj.method()/two)

  i = 1
  eq 2, (4)/2/i
  eq 1, i/i/i

  a = ''
  a += ' ' until /   /.test a
  eq a, '   '

  a = if /=/.test '=' then yes else no
  eq a, yes

  a = if !/=/.test '=' then yes else no
  eq a, no

  #3182:
  match = 'foo=bar'.match /=/
  eq match[0], '='

  #3410:
  ok ' '.match(/ /)[0] is ' '


test "division vs regex after a callable token", ->
  b = 2
  g = 1
  r = (r) -> r.test 'b'

  a = 4
  eq 2, a / b/g
  eq 2, a/b/g
  eq 2, a/ b/g
  eq true, r /b/g
  eq 2, (1 + 3) / b/g
  eq 2, (1 + 3)/b/g
  eq 2, (1 + 3)/ b/g
  eq true, (r) /b/g
  eq 2, [4][0] / b/g
  eq 2, [4][0]/b/g
  eq 2, [4][0]/ b/g
  eq true, [r][0] /b/g
  eq 0.5, 4? / b/g
  eq 0.5, 4?/b/g
  eq 0.5, 4?/ b/g
  eq true, r? /b/g
  (->
    eq 2, @ / b/g
    eq 2, @/b/g
    eq 2, @/ b/g
  ).call 4
  (->
    eq true, @ /b/g
  ).call r
  (->
    eq 2, this / b/g
    eq 2, this/b/g
    eq 2, this/ b/g
  ).call 4
  (->
    eq true, this /b/g
  ).call r
  class A
    p: (regex) -> if regex then r regex else 4
  class B extends A
    p: ->
      eq 2, super() / b/g
      eq 2, super()/b/g
      eq 2, super()/ b/g
      eq true, super /b/g
  new B().p()

test "always division and never regex after some tokens", ->
  b = 2
  g = 1

  eq 2, 4 / b/g
  eq 2, 4/b/g
  eq 2, 4/ b/g
  eq 2, 4 /b/g
  eq 2, "4" / b/g
  eq 2, "4"/b/g
  eq 2, "4"/ b/g
  eq 2, "4" /b/g
  eq 20, "4#{0}" / b/g
  eq 20, "4#{0}"/b/g
  eq 20, "4#{0}"/ b/g
  eq 20, "4#{0}" /b/g
  ok isNaN /a/ / b/g
  ok isNaN /a/i / b/g
  ok isNaN /a//b/g
  ok isNaN /a/i/b/g
  ok isNaN /a// b/g
  ok isNaN /a/i/ b/g
  ok isNaN /a/ /b/g
  ok isNaN /a/i /b/g
  eq 0.5, true / b/g
  eq 0.5, true/b/g
  eq 0.5, true/ b/g
  eq 0.5, true /b/g
  eq 0, false / b/g
  eq 0, false/b/g
  eq 0, false/ b/g
  eq 0, false /b/g
  eq 0, null / b/g
  eq 0, null/b/g
  eq 0, null/ b/g
  eq 0, null /b/g
  ok isNaN undefined / b/g
  ok isNaN undefined/b/g
  ok isNaN undefined/ b/g
  ok isNaN undefined /b/g
  ok isNaN {a: 4} / b/g
  ok isNaN {a: 4}/b/g
  ok isNaN {a: 4}/ b/g
  ok isNaN {a: 4} /b/g
  o = prototype: 4
  eq 2, o:: / b/g
  eq 2, o::/b/g
  eq 2, o::/ b/g
  eq 2, o:: /b/g
  i = 4
  eq 2.0, i++ / b/g
  eq 2.5, i++/b/g
  eq 3.0, i++/ b/g
  eq 3.5, i++ /b/g
  eq 4.0, i-- / b/g
  eq 3.5, i--/b/g
  eq 3.0, i--/ b/g
  eq 2.5, i-- /b/g

test "compound division vs regex", ->
  c = 4
  i = 2

  a = 10
  b = a /= c / i
  eq a, 5

  a = 10
  b = a /= c /i
  eq a, 5

  a = 10
  b = a	/=	c /i # Tabs.
  eq a, 5

  a = 10
  b = a /= c /i # Non-breaking spaces.
  eq a, 5

  a = 10
  b = a/= c /i
  eq a, 5

  a = 10
  b = a/=c/i
  eq a, 5

  a = (regex) -> regex.test '=C '
  b = a /=c /i
  eq b, true

  a = (regex) -> regex.test '= C '
  # Use parentheses to disambiguate.
  b = a(/= c /i)
  eq b, true
  b = a(/= c /)
  eq b, false
  b = a (/= c /)
  eq b, false
  # Escape to disambiguate.
  b = a /\= c /i
  eq b, true
  b = a /\= c /
  eq b, false

test "#764: regular expressions should be indexable", ->
  eq /0/['source'], ///#{0}///['source']

test "#584: slashes are allowed unescaped in character classes", ->
  ok /^a\/[/]b$/.test 'a//b'

test "does not allow to escape newlines", ->
  throwsCompileError '/a\\\nb/'


# Heregexe(n|s)

test "a heregex will ignore whitespace and comments", ->
  eq /^I'm\x20+[a]\s+Heregex?\/\/\//gim + '', ///
    ^ I'm \x20+ [a] \s+
    Heregex? / // # or not
  ///gim + ''

test "an empty heregex will compile to an empty, non-capturing group", ->
  eq /(?:)/ + '', ///  /// + ''
  eq /(?:)/ + '', ////// + ''

test "heregex starting with slashes", ->
  ok /////a/\////.test ' //a// '

test '#2388: `///` in heregex interpolations', ->
  ok ///a#{///b///}c///.test ' /a/b/c/ '
  ws = ' \t'
  scan = (regex) -> regex.exec('\t  foo')[0]
  eq '/\t  /', /// #{scan /// [#{ws}]* ///} /// + ''

test "regexes are not callable", ->
  throwsCompileError '/a/()'
  throwsCompileError '///a#{b}///()'
  throwsCompileError '/a/ 1'
  throwsCompileError '///a#{b}/// 1'
  throwsCompileError '''
    /a/
       k: v
  '''
  throwsCompileError '''
    ///a#{b}///
       k: v
  '''

test "backreferences", ->
  ok /(a)(b)\2\1/.test 'abba'

test "#3795: Escape otherwise invalid characters", ->
  ok (/ /).test '\u2028'
  ok (/ /).test '\u2029'
  ok ///\ ///.test '\u2028'
  ok ///\ ///.test '\u2029'
  ok ///a b///.test 'ab' # The space is U+2028.
  ok ///a b///.test 'ab' # The space is U+2029.
  ok ///\0
      1///.test '\x001'

  a = 'a'
  ok ///#{a} b///.test 'ab' # The space is U+2028.
  ok ///#{a} b///.test 'ab' # The space is U+2029.
  ok ///#{a}\ ///.test 'a\u2028'
  ok ///#{a}\ ///.test 'a\u2029'
  ok ///#{a}\0
      1///.test 'a\x001'

test "#4248: Unicode code point escapes", ->
  ok /a\u{1ab}c/u.test 'a\u01abc'
  ok ///#{ 'a' }\u{000001ab}c///u.test 'a\u{1ab}c'
  ok ///a\u{000001ab}c///u.test 'a\u{1ab}c'
  ok /a\u{12345}c/u.test 'a\ud808\udf45c'

  # and now without u flag
  ok /a\u{1ab}c/.test 'a\u01abc'
  ok ///#{ 'a' }\u{000001ab}c///.test 'a\u{1ab}c'
  ok ///a\u{000001ab}c///.test 'a\u{1ab}c'
  ok /a\u{12345}c/.test 'a\ud808\udf45c'

  # rewrite code point escapes unless u flag is set
  eqJS """
    /\\u{bcdef}\\u{abc}/u
  """,
  """
    /\\u{bcdef}\\u{abc}/u;
  """

  eqJS """
    ///#{ 'a' }\\u{bcdef}///
  """,
  """
    /a\\udab3\\uddef/;
  """

test "#4811, heregex comments with ///", ->
  eqJS """
    ///
      a | # comment with ///
      b   # /// 'heregex' in comment will be consumed
    ///
  """,
  """
   // comment with ///
   // /// 'heregex' in comment will be consumed
   /a|b/;
  """

</script>
<script type="text/x-coffeescript" class="test" id="regex_dotall">
# Regex “dotall” flag, or `s`, is only supported in Node 9+, so put tests for
# the feature in their own file. The feature detection in `Cakefile` that
# causes this test to load is adapted from
# https://github.com/tc39/proposal-regexp-dotall-flag#proposed-solution.

test "dotall flag", ->
  doesNotThrow -> /a.b/s.test 'a\nb'

</script>
<script type="text/x-coffeescript" class="test" id="repl">
return if global.testingBrowser

os = require 'os'
fs = require 'fs'
path = require 'path'

# REPL
# ----
Stream = require 'stream'

class MockInputStream extends Stream
  constructor: ->
    super()
    @readable = true

  resume: ->

  emitLine: (val) ->
    @emit 'data', Buffer.from("#{val}\n")

class MockOutputStream extends Stream
  constructor: ->
    super()
    @writable = true
    @written = []

  write: (data) ->
    # console.log 'output write', arguments
    @written.push data

  lastWrite: (fromEnd = -1) ->
    @written[@written.length - 1 + fromEnd].replace /\r?\n$/, ''

# Create a dummy history file
historyFile = path.join os.tmpdir(), '.coffee_history_test'
fs.writeFileSync historyFile, '1 + 2\n'

testRepl = (desc, fn) ->
  input = new MockInputStream
  output = new MockOutputStream
  repl = Repl.start {input, output, historyFile}
  test desc, -> fn input, output, repl

ctrlV = { ctrl: true, name: 'v'}


testRepl 'reads history file', (input, output, repl) ->
  input.emitLine repl.history[0]
  eq '3', output.lastWrite()

testRepl "starts with coffee prompt", (input, output) ->
  eq 'coffee> ', output.lastWrite(0)

testRepl "writes eval to output", (input, output) ->
  input.emitLine '1+1'
  eq '2', output.lastWrite()

testRepl "comments are ignored", (input, output) ->
  input.emitLine '1 + 1 #foo'
  eq '2', output.lastWrite()

testRepl "output in inspect mode", (input, output) ->
  input.emitLine '"1 + 1\\n"'
  eq "'1 + 1\\n'", output.lastWrite()

testRepl "variables are saved", (input, output) ->
  input.emitLine "foo = 'foo'"
  input.emitLine 'foobar = "#{foo}bar"'
  eq "'foobar'", output.lastWrite()

testRepl "empty command evaluates to undefined", (input, output) ->
  # A regression fixed in Node 5.11.0 broke the handling of pressing enter in
  # the Node REPL; see https://github.com/nodejs/node/pull/6090 and
  # https://github.com/jashkenas/coffeescript/issues/4502.
  # Just skip this test for versions of Node < 6.
  return if parseInt(process.versions.node.split('.')[0], 10) < 6
  input.emitLine ''
  eq 'undefined', output.lastWrite()

testRepl "#4763: comment evaluates to undefined", (input, output) ->
  input.emitLine '# comment'
  eq 'undefined', output.lastWrite()

testRepl "#4763: multiple comments evaluate to undefined", (input, output) ->
  input.emitLine '### a ### ### b ### # c'
  eq 'undefined', output.lastWrite()

testRepl "ctrl-v toggles multiline prompt", (input, output) ->
  input.emit 'keypress', null, ctrlV
  eq '------> ', output.lastWrite(0)
  input.emit 'keypress', null, ctrlV
  eq 'coffee> ', output.lastWrite(0)

testRepl "multiline continuation changes prompt", (input, output) ->
  input.emit 'keypress', null, ctrlV
  input.emitLine ''
  eq '....... ', output.lastWrite(0)

testRepl "evaluates multiline", (input, output) ->
  # Stubs. Could assert on their use.
  output.cursorTo = (pos) ->
  output.clearLine = ->

  input.emit 'keypress', null, ctrlV
  input.emitLine 'do ->'
  input.emitLine '  1 + 1'
  input.emit 'keypress', null, ctrlV
  eq '2', output.lastWrite()

testRepl "variables in scope are preserved", (input, output) ->
  input.emitLine 'a = 1'
  input.emitLine 'do -> a = 2'
  input.emitLine 'a'
  eq '2', output.lastWrite()

testRepl "existential assignment of previously declared variable", (input, output) ->
  input.emitLine 'a = null'
  input.emitLine 'a ?= 42'
  eq '42', output.lastWrite()

testRepl "keeps running after runtime error", (input, output) ->
  input.emitLine 'a = b'
  input.emitLine 'a'
  eq 'undefined', output.lastWrite()

testRepl "#4604: wraps an async function", (input, output) ->
  return unless try new Function 'async () => {}' # Feature detect support for async functions.
  input.emitLine 'await new Promise (resolve) -> setTimeout (-> resolve 33), 10'
  setTimeout ->
    eq '33', output.lastWrite()
  , 20

testRepl "transpile REPL", (input, output) ->
  input.emitLine 'require("./test/importing/transpile_import").getSep()'
  eq "'#{path.sep.replace '\\', '\\\\'}'", output.lastWrite()

process.on 'exit', ->
  try
    fs.unlinkSync historyFile
  catch exception # Already deleted, nothing else to do.

</script>
<script type="text/x-coffeescript" class="test" id="scope">
# Scope
# -----

# * Variable Safety
# * Variable Shadowing
# * Auto-closure (`do`)
# * Global Scope Leaks

test "reference `arguments` inside of functions", ->
  sumOfArgs = ->
    sum = (a,b) -> a + b
    sum = 0
    sum += num for num in arguments
    sum
  eq 10, sumOfArgs(0, 1, 2, 3, 4)

test "assignment to an Object.prototype-named variable should not leak to outer scope", ->
  # FIXME: fails on IE
  (->
    constructor = 'word'
  )()
  ok constructor isnt 'word'

test "siblings of splat parameters shouldn't leak to surrounding scope", ->
  x = 10
  oops = (x, args...) ->
  oops(20, 1, 2, 3)
  eq x, 10

test "catch statements should introduce their argument to scope", ->
  try throw ''
  catch e
    do -> e = 5
    eq 5, e

test "loop variable should be accessible after for-of loop", ->
  d = (x for x of {1:'a',2:'b'})
  ok x in ['1','2']

test "loop variable should be accessible after for-in loop", ->
  d = (x for x in [1,2])
  eq x, 2

test "loop variable should be accessible after for-from loop", ->
  d = (x for x from [1,2])
  eq x, 2

class Array then slice: fail # needs to be global
class Object then hasOwnProperty: fail
test "#1973: redefining Array/Object constructors shouldn't confuse __X helpers", ->
  arr = [1..4]
  arrayEq [3, 4], arr[2..]
  obj = {arr}
  for own k of obj
    eq arr, obj[k]

test "#2255: global leak with splatted @-params", ->
  ok not x?
  arrayEq [0], ((@x...) -> @x).call {}, 0
  ok not x?

test "#1183: super + fat arrows", ->
  dolater = (cb) -> cb()

  class A
    constructor: ->
      @_i = 0
    foo : (cb) ->
      dolater =>
        @_i += 1
        cb()

  class B extends A
    constructor : ->
      super()
    foo : (cb) ->
      dolater =>
        dolater =>
          @_i += 2
          super cb

  b = new B
  b.foo => eq b._i, 3

test "#1183: super + wrap", ->
  class A
    m : -> 10

  class B extends A
    constructor : -> super()
    m: -> r = try super()
    m: -> r = super()

  eq (new B).m(), 10

test "#1183: super + closures", ->
  class A
    constructor: ->
      @i = 10
    foo : -> @i

  class B extends A
    foo : ->
      ret = switch 1
        when 0 then 0
        when 1 then super()
      ret
  eq (new B).foo(), 10

test "#2331: bound super regression", ->
  class A
    @value = 'A'
    method: -> @constructor.value

  class B extends A
    method: => super()

  eq (new B).method(), 'A'

test "#3259: leak with @-params within destructured parameters", ->
  fn = ({@foo}, [@bar], [{@baz}]) ->
    foo = bar = baz = false

  fn.call {}, {foo: 'foo'}, ['bar'], [{baz: 'baz'}]

  eq 'undefined', typeof foo
  eq 'undefined', typeof bar
  eq 'undefined', typeof baz

</script>
<script type="text/x-coffeescript" class="test" id="slicing_and_splicing">
# Slicing and Splicing
# --------------------

# * Slicing
# * Splicing

# shared array
shared = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Slicing

test "basic slicing", ->
  arrayEq [7, 8, 9]   , shared[7..9]
  arrayEq [2, 3]      , shared[2...4]
  arrayEq [2, 3, 4, 5], shared[2...6]

test "slicing with variables as endpoints", ->
  [a, b] = [1, 4]
  arrayEq [1, 2, 3, 4], shared[a..b]
  arrayEq [1, 2, 3]   , shared[a...b]

test "slicing with expressions as endpoints", ->
  [a, b] = [1, 3]
  arrayEq [2, 3, 4, 5, 6], shared[(a+1)..2*b]
  arrayEq [2, 3, 4, 5]   , shared[a+1...(2*b)]

test "unbounded slicing", ->
  arrayEq [7, 8, 9]   , shared[7..]
  arrayEq [8, 9]      , shared[-2..]
  arrayEq [9]         , shared[-1...]
  arrayEq [0, 1, 2]   , shared[...3]
  arrayEq [0, 1, 2, 3], shared[..-7]

  arrayEq shared      , shared[..-1]
  arrayEq shared[0..8], shared[...-1]

  for a in [-shared.length..shared.length]
    arrayEq shared[a..] , shared[a...]
  for a in [-shared.length+1...shared.length]
    arrayEq shared[..a][...-1] , shared[...a]

  arrayEq [1, 2, 3], [1, 2, 3][..]

test "#930, #835, #831, #746 #624: inclusive slices to -1 should slice to end", ->
  arrayEq shared, shared[0..-1]
  arrayEq shared, shared[..-1]
  arrayEq shared.slice(1,shared.length), shared[1..-1]

test "string slicing", ->
  str = "abcdefghijklmnopqrstuvwxyz"
  ok str[1...1] is ""
  ok str[1..1] is "b"
  ok str[1...5] is "bcde"
  ok str[0..4] is "abcde"
  ok str[-5..] is "vwxyz"

test "#1722: operator precedence in unbounded slice compilation", ->
  list = [0..9]
  n = 2 # some truthy number in `list`
  arrayEq [0..n], list[..n]
  arrayEq [0..n], list[..n or 0]
  arrayEq [0..n], list[..if n then n else 0]

test "#2349: inclusive slicing to numeric strings", ->
  arrayEq [0, 1], [0..10][.."1"]

test "#4631: slicing with space before and/or after the dots", ->
  a = (s) -> s
  b = [4, 5, 6]
  c = [7, 8, 9]
  arrayEq [2, 3, 4], shared[2 ... 5]
  arrayEq [3, 4, 5], shared[3... 6]
  arrayEq [4, 5, 6], shared[4 ...7]
  arrayEq shared[(a b...)...(a c...)]  , shared[(a ...b)...(a ...c)]
  arrayEq shared[(a b...) ... (a c...)], shared[(a ...b) ... (a ...c)]
  arrayEq shared[(a b...)... (a c...)] , shared[(a ...b)... (a ...c)]
  arrayEq shared[(a b...) ...(a c...)] , shared[(a ...b) ...(a ...c)]


# Splicing

test "basic splicing", ->
  ary = [0..9]
  ary[5..9] = [0, 0, 0]
  arrayEq [0, 1, 2, 3, 4, 0, 0, 0], ary

  ary = [0..9]
  ary[2...8] = []
  arrayEq [0, 1, 8, 9], ary

test "unbounded splicing", ->
  ary = [0..9]
  ary[3..] = [9, 8, 7]
  arrayEq [0, 1, 2, 9, 8, 7]. ary

  ary[...3] = [7, 8, 9]
  arrayEq [7, 8, 9, 9, 8, 7], ary

  ary[..] = [1, 2, 3]
  arrayEq [1, 2, 3], ary

test "splicing with variables as endpoints", ->
  [a, b] = [1, 8]

  ary = [0..9]
  ary[a..b] = [2, 3]
  arrayEq [0, 2, 3, 9], ary

  ary = [0..9]
  ary[a...b] = [5]
  arrayEq [0, 5, 8, 9], ary

test "splicing with expressions as endpoints", ->
  [a, b] = [1, 3]

  ary = [0..9]
  ary[ a+1 .. 2*b+1 ] = [4]
  arrayEq [0, 1, 4, 8, 9], ary

  ary = [0..9]
  ary[a+1...2*b+1] = [4]
  arrayEq [0, 1, 4, 7, 8, 9], ary

test "splicing to the end, against a one-time function", ->
  ary = null
  fn = ->
    if ary
      throw 'err'
    else
      ary = [1, 2, 3]

  fn()[0..] = 1

  arrayEq ary, [1]

test "the return value of a splice literal should be the RHS", ->
  ary = [0, 0, 0]
  eq (ary[0..1] = 2), 2

  ary = [0, 0, 0]
  eq (ary[0..] = 3), 3

  arrayEq [ary[0..0] = 0], [0]

test "#1723: operator precedence in unbounded splice compilation", ->
  n = 4 # some truthy number in `list`

  list = [0..9]
  list[..n] = n
  arrayEq [n..9], list

  list = [0..9]
  list[..n or 0] = n
  arrayEq [n..9], list

  list = [0..9]
  list[..if n then n else 0] = n
  arrayEq [n..9], list

test "#2953: methods on endpoints in assignment from array splice literal", ->
  list = [0..9]

  Number.prototype.same = -> this
  list[1.same()...9.same()] = 5
  delete Number.prototype.same

  arrayEq [0, 5, 9], list

test "#1726: `Op` expression in property access causes unexpected results", ->
  a = [0..2]
  arrayEq a, a[(!1 in a)..]
  arrayEq a, a[!1 in a..]
  arrayEq a[(!1 in a)..], a[(!1 in a)..]

</script>
<script type="text/x-coffeescript" class="test" id="soaks">
# Soaks
# -----

# * Soaked Property Access
# * Soaked Method Invocation
# * Soaked Function Invocation


# Soaked Property Access

test "soaked property access", ->
  nonce = {}
  obj = a: b: nonce
  eq nonce    , obj?.a.b
  eq nonce    , obj?['a'].b
  eq nonce    , obj.a?.b
  eq nonce    , obj?.a?['b']
  eq undefined, obj?.a?.non?.existent?.property

test "soaked property access caches method calls", ->
  nonce ={}
  obj = fn: -> a: nonce
  eq nonce    , obj.fn()?.a
  eq undefined, obj.fn()?.b

test "soaked property access caching", ->
  nonce = {}
  counter = 0
  fn = ->
    counter++
    'self'
  obj =
    self: -> @
    prop: nonce
  eq nonce, obj[fn()]()[fn()]()[fn()]()?.prop
  eq 3, counter

test "method calls on soaked methods", ->
  nonce = {}
  obj = null
  eq undefined, obj?.a().b()
  obj = a: -> b: -> nonce
  eq nonce    , obj?.a().b()

test "postfix existential operator mixes well with soaked property accesses", ->
  eq false, nonexistent?.property?

test "function invocation with soaked property access", ->
  id = (_) -> _
  eq undefined, id nonexistent?.method()

test "if-to-ternary should safely parenthesize soaked property accesses", ->
  ok (if nonexistent?.property then false else true)

test "#726: don't check for a property on a conditionally-referenced nonexistent thing", ->
  eq undefined, nonexistent?[Date()]

test "#756: conditional assignment edge cases", ->
  # TODO: improve this test
  a = null
  ok isNaN      a?.b.c +  1
  eq undefined, a?.b.c += 1
  eq undefined, ++a?.b.c
  eq undefined, delete a?.b.c

test "operations on soaked properties", ->
  # TODO: improve this test
  a = b: {c: 0}
  eq 1,   a?.b.c +  1
  eq 1,   a?.b.c += 1
  eq 2,   ++a?.b.c
  eq yes, delete a?.b.c


# Soaked Method Invocation

test "soaked method invocation", ->
  nonce = {}
  counter = 0
  obj =
    self: -> @
    increment: -> counter++; @
  eq obj      , obj.self?()
  eq undefined, obj.method?()
  eq nonce    , obj.self?().property = nonce
  eq undefined, obj.method?().property = nonce
  eq obj      , obj.increment().increment().self?()
  eq 2        , counter

test "#733: conditional assignments", ->
  a = b: {c: null}
  eq a.b?.c?(), undefined
  a.b?.c or= (it) -> it
  eq a.b?.c?(1), 1
  eq a.b?.c?([2, 3]...), 2


# Soaked Function Invocation

test "soaked function invocation", ->
  nonce = {}
  id = (_) -> _
  eq nonce    , id?(nonce)
  eq nonce    , (id? nonce)
  eq undefined, nonexistent?(nonce)
  eq undefined, (nonexistent? nonce)

test "soaked function invocation with generated functions", ->
  nonce = {}
  id = (_) -> _
  maybe = (fn, arg) -> if typeof fn is 'function' then () -> fn(arg)
  eq maybe(id, nonce)?(), nonce
  eq (maybe id, nonce)?(), nonce
  eq (maybe false, nonce)?(), undefined

test "soaked constructor invocation", ->
  eq 42       , +new Number? 42
  eq undefined,  new Other?  42

test "soaked constructor invocations with caching and property access", ->
  semaphore = 0
  nonce = {}
  class C
    constructor: ->
      ok false if semaphore
      semaphore++
    prop: nonce
  eq nonce, (new C())?.prop
  eq 1, semaphore

test "soaked function invocation safe on non-functions", ->
  eq undefined, (0)?(1)
  eq undefined, (0)? 1, 2

</script>
<script type="text/x-coffeescript" class="test" id="sourcemap">
return if global.testingBrowser

{spawn, fork} = require('child_process')
SourceMap = require '../src/sourcemap'

vlqEncodedValues = [
    [1, 'C'],
    [-1, 'D'],
    [2, 'E'],
    [-2, 'F'],
    [0, 'A'],
    [16, 'gB'],
    [948, 'o7B']
]

test "encodeVlq tests", ->
  for pair in vlqEncodedValues
    eq ((new SourceMap).encodeVlq pair[0]), pair[1]

test "SourceMap tests", ->
  map = new SourceMap
  map.add [0, 0], [0, 0]
  map.add [1, 5], [2, 4]
  map.add [1, 6], [2, 7]
  map.add [1, 9], [2, 8]
  map.add [3, 0], [3, 4]

  testWithFilenames = map.generate {
    sourceRoot: ''
    sourceFiles: ['source.coffee']
    generatedFile: 'source.js'
  }

  deepEqual testWithFilenames, {
    version: 3
    file: 'source.js'
    sourceRoot: ''
    sources: ['source.coffee']
    names: []
    mappings: 'AAAA;;IACK,GAAC,CAAG;IAET'
  }

  deepEqual map.generate(), {
    version: 3
    file: ''
    sourceRoot: ''
    sources: ['<anonymous>']
    names: []
    mappings: 'AAAA;;IACK,GAAC,CAAG;IAET'
  }

  # Look up a generated column - should get back the original source position.
  arrayEq map.sourceLocation([2,8]), [1,9]

  # Look up a point further along on the same line - should get back the same source position.
  arrayEq map.sourceLocation([2,10]), [1,9]

test "#3075: v3 source map fields", ->
  { js, v3SourceMap, sourceMap } = CoffeeScript.compile 'console.log Date.now()',
    filename: 'tempus_fugit.coffee'
    sourceMap: yes
    sourceRoot: './www_root/coffee/'

  v3SourceMap = JSON.parse v3SourceMap
  arrayEq v3SourceMap.sources, ['tempus_fugit.coffee']
  eq v3SourceMap.sourceRoot, './www_root/coffee/'

test "node --enable-source-map built in stack trace mapping", ->
  new Promise (resolve, reject) ->
    proc = fork './test/importing/error.coffee', [
      '--enable-source-maps'
    ], stdio: 'pipe'

    err = ''
    proc.stderr.setEncoding 'utf8'
    proc.stderr.on 'data', (str) -> err += str
    proc.on 'close', ->
      try
        match = err.match /error\.coffee:(\d+):(\d+)/
        throw new Error err unless match

        [_, line, column] = match
        equal line, 3 # Mapped source line
        equal column, 9 # Mapped source column

        resolve()
      catch exception
        reject exception

if Number(process.versions.node.split('.')[0]) >= 14
  test "NODE_OPTIONS=--enable-source-maps environment variable stack trace mapping", ->
    new Promise (resolve, reject) ->
      proc = fork './test/importing/error.coffee', [],
        env:
          NODE_OPTIONS: '--enable-source-maps'
        stdio: 'pipe'

      err = ''
      proc.stderr.setEncoding 'utf8'
      proc.stderr.on 'data', (str) -> err += str
      proc.on 'close', ->
        try
          match = err.match /error\.coffee:(\d+):(\d+)/
          throw new Error err unless match

          [_, line, column] = match
          equal line, 3 # Mapped source line
          equal column, 9 # Mapped source column

          resolve()
        catch exception
          reject exception

  test "generate correct stack traces with --enable-source-maps from bin/coffee", ->
    new Promise (resolve, reject) ->
      proc = fork 'test/importing/error.coffee',
        ['--enable-source-maps'],
        stdio: 'pipe'

      err = ""
      proc.stderr.setEncoding 'utf8'
      proc.stderr.on 'data', (str) -> err += str
      proc.on 'close', ->
        try
          match = err.match /error\.coffee:(\d+):(\d+)/
          throw new Error err unless match

          [_, line, column] = match
          equal line, 3 # Mapped source line
          equal column, 9 # Mapped source column

          resolve()
        catch exception
          reject exception

test "don't change stack traces if another library has patched `Error.prepareStackTrace`", ->
  new Promise (resolve, reject) ->
    # This uses `spawn` rather than the preferred `fork` because `fork` requires
    # loading code in a separate file. The `--eval` here shows exactly what is
    # executing without indirection.
    proc = spawn 'node', [
      '--eval', """
        const patchedPrepareStackTrace = Error.prepareStackTrace = function() {};
        require('./register.js');
        process.stdout.write(Error.prepareStackTrace === patchedPrepareStackTrace ? 'preserved' : 'overwritten');
      """
    ]

    out = ''
    proc.stdout.setEncoding 'utf8'
    proc.stdout.on 'data', (str) -> out += str

    proc.on 'close', (status) ->
      try
        equal status, 0
        equal out, 'preserved'

        resolve()
      catch exception
        reject exception

test "requiring 'CoffeeScript' doesn't change `Error.prepareStackTrace`", ->
  new Promise (resolve, reject) ->
    # This uses `spawn` rather than the preferred `fork` because `fork` requires
    # loading code in a separate file. The `--eval` here shows exactly what is
    # executing without indirection.
    proc = spawn 'node', [
      '--eval', """
        require('./lib/coffeescript/coffeescript.js');
        process.stdout.write(Error.prepareStackTrace === undefined ? 'unused' : 'defined');
      """
    ]

    out = ''
    proc.stdout.setEncoding 'utf8'
    proc.stdout.on 'data', (str) -> out += str

    proc.on 'close', (status) ->
      try
        equal status, 0
        equal out, 'unused'

        resolve()
      catch exception
        reject exception

</script>
<script type="text/x-coffeescript" class="test" id="strict">
# Strict Early Errors
# -------------------

# The following are prohibited under ES5’s `strict` mode
# * `Octal Integer Literals`
# * `Octal Escape Sequences`
# * duplicate property definitions in `Object Literal`s
# * duplicate formal parameter
# * `delete` operand is a variable
# * `delete` operand is a parameter
# * `delete` operand is `undefined`
# * `Future Reserved Word`s as identifiers: implements, interface, let, package, private, protected, public, static, yield
# * `eval` or `arguments` as `catch` identifier
# * `eval` or `arguments` as formal parameter
# * `eval` or `arguments` as function declaration identifier
# * `eval` or `arguments` as LHS of assignment
# * `eval` or `arguments` as the operand of a post/pre-fix inc/dec-rement expression

# helper to assert that code complies with strict prohibitions
strict = (code, msg) ->
  throwsCompileError code, null, null, msg ? code
strictOk = (code, msg) ->
  doesNotThrowCompileError code, null, msg ? code


test "octal integer literals prohibited", ->
  strict    '01'
  strict    '07777'
  # decimals with a leading '0' are also prohibited
  strict    '09'
  strict    '079'
  strictOk  '`01`'

test "octal escape sequences prohibited", ->
  strict    '"\\1"'
  strict    '"\\7"'
  strict    '"\\001"'
  strict    '"\\777"'
  strict    '"_\\1"'
  strict    '"\\1_"'
  strict    '"_\\1_"'
  strict    '"\\\\\\1"'
  strictOk  '"\\0"'
  eq "\x00", "\0"
  strictOk  '"\\0\\8"'
  eq "\x008", "\0\8"
  strictOk  '"\\8"'
  eq "8", "\8"
  strictOk  '"\\\\1"'
  eq "\\" + "1", "\\1"
  strictOk  '"\\\\\\\\1"'
  eq "\\\\" + "1", "\\\\1"
  strictOk  "`'\\1'`"
  eq "\\" + "1", `"\\1"`

  # Also test other string types.
  strict           "'\\\\\\1'"
  eq "\\\\" + "1", '\\\\1'
  strict           "'''\\\\\\1'''"
  eq "\\\\" + "1", '''\\\\1'''
  strict           '"""\\\\\\1"""'
  eq "\\\\" + "1", """\\\\1"""

test "duplicate formal parameters are prohibited", ->
  nonce = {}
  # a Param can be an Identifier, ThisProperty( @-param ), Array, or Object
  # a Param can also be a splat (...) or an assignment (param=value)
  # the following function expressions should throw errors
  strict '(_,_)->',          'param, param'
  strict '(_,_...)->',       'param, param...'
  strict '(_,_ = true)->',   'param, param='
  strict '(@_,@_)->',        'two @params'
  strict '(@case,@case)->',  'two @reserved'
  strict '(_,{_})->',        'param, {param}'
  strict '(_,{_=true})->',   'param, {param=}'
  strict '({_,_})->',        '{param, param}'
  strict '({_=true,_})->',   '{param=, param}'
  strict '(_,[_])->',        'param, [param]'
  strict '(_,[_=true])->',   'param, [param=]'
  strict '([_,_])->',        '[param, param]'
  strict '([_=true,_])->',   '[param=, param]'
  strict '(_,[_]=true)->',   'param, [param]='
  strict '(_,[_=true]=true)->', 'param, [param=]='
  strict '(_,[@_,{_}])->',   'param, [@param, {param}]'
  strict '(_,[_,{@_}])->',   'param, [param, {@param}]'
  strict '(_,[_,{@_=true}])->', 'param, [param, {@param=}]'
  strict '(_,[_,{_}])->',    'param, [param, {param}]'
  strict '(_,[_,{__}])->',   'param, [param, {param2}]'
  strict '(_,[__,{_}])->',   'param, [param2, {param}]'
  strict '(__,[_,{_}])->',   'param, [param2, {param2}]'
  strict '({0:a,1:a})->',    '{0:param,1:param}'
  strict '(a=b=true,a)->',   'param=assignment, param'
  strict '({a=b=true},a)->', '{param=assignment}, param'
  # the following function expressions should **not** throw errors
  strictOk '(_,@_)->'
  strictOk '(@_,_...)->'
  strictOk '(_,@_ = true)->'
  strictOk '(@_,{_})->'
  strictOk '({_,@_})->'
  strictOk '({_,@_ = true})->'
  strictOk '([_,@_])->'
  strictOk '([_,@_ = true])->'
  strictOk '({},_arg)->'
  strictOk '({},{})->'
  strictOk '([]...,_arg)->'
  strictOk '({}...,_arg)->'
  strictOk '({}...,[],_arg)->'
  strictOk '([]...,{},_arg)->'
  strictOk '(@case,_case)->'
  strictOk '(@case,_case...)->'
  strictOk '(@case...,_case)->'
  strictOk '(_case,@case)->'
  strictOk '(_case,@case...)->'
  strictOk '({a:a})->'
  strictOk '({a:a,a:b})->'

test "`delete` operand restrictions", ->
  strict 'a = 1; delete a'
  strictOk 'delete a' #noop
  strict '(a) -> delete a'
  strict '(a...) -> delete a'
  strict '(a = 1) -> delete a'
  strict '([a]) -> delete a'
  strict '({a}) -> delete a'

test "`Future Reserved Word`s, `eval` and `arguments` restrictions", ->

  access = (keyword, check = strict) ->
    check "#{keyword}.a = 1"
    check "#{keyword}[0] = 1"
  assign = (keyword, check = strict) ->
    check "#{keyword} = 1"
    check "#{keyword} += 1"
    check "#{keyword} -= 1"
    check "#{keyword} *= 1"
    check "#{keyword} /= 1"
    check "#{keyword} ?= 1"
  update = (keyword, check = strict) ->
    check "#{keyword}++"
    check "++#{keyword}"
    check "#{keyword}--"
    check "--#{keyword}"
  destruct = (keyword, check = strict) ->
    check "{#{keyword}}"
    check "o = {#{keyword}}"
  invoke = (keyword, check = strict) ->
    check "#{keyword} yes"
    check "do #{keyword}"
  fnDecl = (keyword, check = strict) ->
    check "class #{keyword}"
  param = (keyword, check = strict) ->
    check "(#{keyword}) ->"
    check "({#{keyword}}) ->"
  prop = (keyword, check = strict) ->
    check "a.#{keyword} = 1"
  tryCatch = (keyword, check = strict) ->
    check "try new Error catch #{keyword}"

  future = 'implements interface let package private protected public static'.split ' '
  for keyword in future
    access   keyword
    assign   keyword
    update   keyword
    destruct keyword
    invoke   keyword
    fnDecl   keyword
    param    keyword
    prop     keyword, strictOk
    tryCatch keyword

  for keyword in ['eval', 'arguments']
    access   keyword, strictOk
    assign   keyword
    update   keyword
    destruct keyword, strictOk
    invoke   keyword, strictOk
    fnDecl   keyword
    param    keyword
    prop     keyword, strictOk
    tryCatch keyword

</script>
<script type="text/x-coffeescript" class="test" id="strings">
# String Literals
# ---------------

# TODO: refactor string literal tests
# TODO: add indexing and method invocation tests: "string"["toString"] is String::toString, "string".toString() is "string"

# * Strings
# * Heredocs

test "backslash escapes", ->
  eq "\\/\\\\", /\/\\/.source

eq '(((dollars)))', '\(\(\(dollars\)\)\)'
eq 'one two three', "one
 two
 three"
eq "four five", 'four

 five'

test "#3229, multiline strings", ->
  # Separate lines by default by a single space in literal strings.
  eq 'one
      two', 'one two'
  eq "one
      two", 'one two'
  eq '
        a
        b
    ', 'a b'
  eq "
        a
        b
    ", 'a b'
  eq 'one

        two', 'one two'
  eq "one

        two", 'one two'
  eq '
    indentation
      doesn\'t
  matter', 'indentation doesn\'t matter'
  eq 'trailing ws      
    doesn\'t matter', 'trailing ws doesn\'t matter'

  # Use backslashes at the end of a line to specify whitespace between lines.
  eq 'a \
      b\
      c  \
      d', 'a bc  d'
  eq "a \
      b\
      c  \
      d", 'a bc  d'
  eq 'ignore  \  
      trailing whitespace', 'ignore  trailing whitespace'

  # Backslash at the beginning of a literal string.
  eq '\
      ok', 'ok'
  eq '  \
      ok', '  ok'

  # #1273, empty strings.
  eq '\
     ', ''
  eq '
     ', ''
  eq '
          ', ''
  eq '   ', '   '

  # Same behavior in interpolated strings.
  eq "interpolation #{1}
      follows #{2}  \
      too #{3}\
      !", 'interpolation 1 follows 2  too 3!'
  eq "a #{
    'string ' + "inside
                 interpolation"
    }", "a string inside interpolation"
  eq "
      #{1}
     ", '1'

  # Handle escaped backslashes correctly.
  eq '\\', `'\\'`
  eq 'escaped backslash at EOL\\
      next line', 'escaped backslash at EOL\\ next line'
  eq '\\
      next line', '\\ next line'
  eq '\\
     ', '\\'
  eq '\\\\\\
     ', '\\\\\\'
  eq "#{1}\\
      after interpolation", '1\\ after interpolation'
  eq 'escaped backslash before slash\\  \
      next line', 'escaped backslash before slash\\  next line'
  eq 'triple backslash\\\
      next line', 'triple backslash\\next line'
  eq 'several escaped backslashes\\\\\\
      ok', 'several escaped backslashes\\\\\\ ok'
  eq 'several escaped backslashes slash\\\\\\\
      ok', 'several escaped backslashes slash\\\\\\ok'
  eq 'several escaped backslashes with trailing ws \\\\\\   
      ok', 'several escaped backslashes with trailing ws \\\\\\ ok'

  # Backslashes at beginning of lines.
  eq 'first line
      \   backslash at BOL', 'first line \   backslash at BOL'
  eq 'first line\
      \   backslash at BOL', 'first line\   backslash at BOL'

  # Backslashes at end of strings.
  eq 'first line \ ', 'first line  '
  eq 'first line
      second line \
      ', 'first line second line '
  eq 'first line
      second line
      \
      ', 'first line second line'
  eq 'first line
      second line

        \

      ', 'first line second line'

  # Edge case.
  eq 'lone

        \

        backslash', 'lone backslash'

test "#3249, escape newlines in heredocs with backslashes", ->
  # Ignore escaped newlines
  eq '''
    Set whitespace      \
       <- this is ignored\  
           none
      normal indentation
    ''', 'Set whitespace      <- this is ignorednone\n  normal indentation'
  eq """
    Set whitespace      \
       <- this is ignored\  
           none
      normal indentation
    """, 'Set whitespace      <- this is ignorednone\n  normal indentation'

  # Changed from #647, trailing backslash.
  eq '''
  Hello, World\

  ''', 'Hello, World'
  eq '''
    \\
  ''', '\\'

  # Backslash at the beginning of a literal string.
  eq '''\
      ok''', 'ok'
  eq '''  \
      ok''', '  ok'

  # Same behavior in interpolated strings.
  eq """
    interpolation #{1}
      follows #{2}  \
        too #{3}\
    !
  """, 'interpolation 1\n  follows 2  too 3!'
  eq """

    #{1} #{2}

    """, '\n1 2\n'

  # Handle escaped backslashes correctly.
  eq '''
    escaped backslash at EOL\\
      next line
  ''', 'escaped backslash at EOL\\\n  next line'
  eq '''\\

     ''', '\\\n'

  # Backslashes at beginning of lines.
  eq '''first line
      \   backslash at BOL''', 'first line\n\   backslash at BOL'
  eq """first line\
      \   backslash at BOL""", 'first line\   backslash at BOL'

  # Backslashes at end of strings.
  eq '''first line \ ''', 'first line  '
  eq '''
    first line
    second line \
  ''', 'first line\nsecond line '
  eq '''
    first line
    second line
    \
  ''', 'first line\nsecond line'
  eq '''
    first line
    second line

      \

  ''', 'first line\nsecond line\n'

  # Edge cases.
  eq '''lone

          \



        backslash''', 'lone\n\n  backslash'
  eq '''\
     ''', ''

test '#2388: `"""` in heredoc interpolations', ->
  eq """a heredoc #{
      "inside \
        interpolation"
    }""", "a heredoc inside interpolation"
  eq """a#{"""b"""}c""", 'abc'
  eq """#{""""""}""", ''

test "trailing whitespace", ->
  testTrailing = (str, expected) ->
    eq CoffeeScript.eval(str.replace /\|$/gm, ''), expected
  testTrailing '''"   |
      |
    a   |
           |
  "''', 'a'
  testTrailing """'''   |
      |
    a   |
           |
  '''""", '  \na   \n       '

#647
eq "''Hello, World\\''", '''
'\'Hello, World\\\''
'''
eq '""Hello, World\\""', """
"\"Hello, World\\\""
"""

test "#1273, escaping quotes at the end of heredocs.", ->
  # """\""" no longer compiles
  eq """\\""", '\\'
  eq """\\\"""", '\\\"'

a = """
    basic heredoc
    on two lines
    """
ok a is "basic heredoc\non two lines"

a = '''
    a
      "b
    c
    '''
ok a is "a\n  \"b\nc"

a = """
a
 b
  c
"""
ok a is "a\n b\n  c"

a = '''one-liner'''
ok a is 'one-liner'

a = """
      out
      here
"""
ok a is "out\nhere"

a = '''
       a
     b
   c
    '''
ok a is "    a\n  b\nc"

a = '''
a


b c
'''
ok a is "a\n\n\nb c"

a = '''more"than"one"quote'''
ok a is 'more"than"one"quote'

a = '''here's an apostrophe'''
ok a is "here's an apostrophe"

a = """""surrounded by two quotes"\""""
ok a is '""surrounded by two quotes""'

a = '''''surrounded by two apostrophes'\''''
ok a is "''surrounded by two apostrophes''"

# The indentation detector ignores blank lines without trailing whitespace
a = """
    one
    two

    """
ok a is "one\ntwo\n"

eq ''' line 0
  should not be relevant
    to the indent level
''', ' line 0\nshould not be relevant\n  to the indent level'

eq """
  interpolation #{
 "contents"
 }
  should not be relevant
    to the indent level
""", 'interpolation contents\nshould not be relevant\n  to the indent level'

eq ''' '\\\' ''', " '\\' "
eq """ "\\\" """, ' "\\" '

eq '''  <- keep these spaces ->  ''', '  <- keep these spaces ->  '

eq '''undefined''', 'undefined'
eq """undefined""", 'undefined'


test "#1046, empty string interpolations", ->
  eq "#{ }", ''

test "strings are not callable", ->
  throwsCompileError '"a"()'
  throwsCompileError '"a#{b}"()'
  throwsCompileError '"a" 1'
  throwsCompileError '"a#{b}" 1'
  throwsCompileError '''
    "a"
       k: v
  '''
  throwsCompileError '''
    "a#{b}"
       k: v
  '''

test "#3795: Escape otherwise invalid characters", ->
  eq ' ', '\u2028'
  eq ' ', '\u2029'
  eq '\0\
      1', '\x001'
  eq " ", '\u2028'
  eq " ", '\u2029'
  eq "\0\
      1", '\x001'
  eq "\0\
      9", '\x009'
  eq "\0#{}0", '\x000'
  eq ''' ''', '\u2028'
  eq ''' ''', '\u2029'
  eq '''\0\
      1''', '\x001'
  eq '''\0\
      9''', '\x009'
  eq """\0#{}1""", '\x001'
  eq """ """, '\u2028'
  eq """ """, '\u2029'
  eq """\0\
      1""", '\x001'

  a = 'a'
  eq "#{a} ", 'a\u2028'
  eq "#{a} ", 'a\u2029'
  eq "#{a}\0\
      1", 'a\0' + '1'
  eq """#{a} """, 'a\u2028'
  eq """#{a} """, 'a\u2029'
  eq """#{a}\0\
      1""", 'a\0' + '1'

test "#4314: Whitespace less than or equal to stripped indentation", ->
  # The odd indentation is intentional here, to test 1-space indentation.
  eq ' ', """
 #{} #{}
"""

  eq '1 2  3   4    5     end\na 0     b', """
    #{1} #{2}  #{3}   #{4}    #{5}     end
    a #{0}     b"""

test "#4248: Unicode code point escapes", ->
  eq '\u01ab\u00cd', '\u{1ab}\u{cd}'
  eq '\u01ab', '\u{000001ab}'
  eq 'a\u01ab', "#{ 'a' }\u{1ab}"
  eq '\u01abc', '''\u{01ab}c'''
  eq '\u01abc', """\u{1ab}#{ 'c' }"""
  eq '\udab3\uddef', '\u{bcdef}'
  eq '\udab3\uddef', '\u{0000bcdef}'
  eq 'a\udab3\uddef', "#{ 'a' }\u{bcdef}"
  eq '\udab3\uddefc', '''\u{0bcdef}c'''
  eq '\udab3\uddefc', """\u{bcdef}#{ 'c' }"""
  eq '\\u{123456}', "#{'\\'}#{'u{123456}'}"

  # don't rewrite code point escapes
  eqJS """
    '\\u{bcdef}\\u{abc}'
  """,
  """
    '\\u{bcdef}\\u{abc}';
  """

  eqJS """
    "#{ 'a' }\\u{bcdef}"
  """,
  """
    "a\\u{bcdef}";
  """

</script>
<script type="text/x-coffeescript" class="test" id="tagged_template_literals">
# Tagged template literals
# ------------------------

# NOTES:
# A tagged template literal is a string that is passed to a prefixing function for
# post-processing. There's a bunch of different angles that need testing:
# - Prefixing function, which can be any form of function call:
#   - function: func'Hello'
#   - object property with dot notation: outerobj.obj.func'Hello'
#   - object property with bracket notation: outerobj['obj']['func']'Hello'
# - String form: single quotes, double quotes and block strings
# - String is single-line or multi-line
# - String is interpolated or not

func = (text, expressions...) ->
  "text: [#{text.join '|'}] expressions: [#{expressions.join '|'}]"

outerobj =
  obj:
    func: func
    f: -> func

# Example use
test "tagged template literal for html templating", ->
  html = (htmlFragments, expressions...) ->
    htmlFragments.reduce (fullHtml, htmlFragment, i) ->
      fullHtml + "#{expressions[i - 1]}#{htmlFragment}"

  state =
    name: 'Greg'
    adjective: 'awesome'

  eq """
      <p>
        Hi Greg. You're looking awesome!
      </p>
    """,
    html"""
      <p>
        Hi #{state.name}. You're looking #{state.adjective}!
      </p>
    """

# Simple, non-interpolated strings
test "tagged template literal with a single-line single-quote string", ->
  eq 'text: [single-line single quotes] expressions: []',
  func'single-line single quotes'

test "tagged template literal with a single-line double-quote string", ->
  eq 'text: [single-line double quotes] expressions: []',
  func"single-line double quotes"

test "tagged template literal with a single-line single-quote block string", ->
  eq 'text: [single-line block string] expressions: []',
  func'''single-line block string'''

test "tagged template literal with a single-line double-quote block string", ->
  eq 'text: [single-line block string] expressions: []',
  func"""single-line block string"""

test "tagged template literal with a multi-line single-quote string", ->
  eq 'text: [multi-line single quotes] expressions: []',
  func'multi-line
                                                              single quotes'

test "tagged template literal with a multi-line double-quote string", ->
  eq 'text: [multi-line double quotes] expressions: []',
  func"multi-line
       double quotes"

test "tagged template literal with a multi-line single-quote block string", ->
  eq 'text: [multi-line\nblock string] expressions: []',
  func'''
      multi-line
      block string
      '''

test "tagged template literal with a multi-line double-quote block string", ->
  eq 'text: [multi-line\nblock string] expressions: []',
  func"""
      multi-line
      block string
      """

# Interpolated strings with expressions
test "tagged template literal with a single-line double-quote interpolated string", ->
  eq 'text: [single-line | double quotes | interpolation] expressions: [36|42]',
  func"single-line #{6 * 6} double quotes #{6 * 7} interpolation"

test "tagged template literal with a single-line double-quote block interpolated string", ->
  eq 'text: [single-line | block string | interpolation] expressions: [incredible|48]',
  func"""single-line #{'incredible'} block string #{6 * 8} interpolation"""

test "tagged template literal with a multi-line double-quote interpolated string", ->
  eq 'text: [multi-line | double quotes | interpolation] expressions: [2|awesome]',
  func"multi-line #{4/2}
       double quotes #{'awesome'} interpolation"

test "tagged template literal with a multi-line double-quote block interpolated string", ->
  eq 'text: [multi-line |\nblock string |] expressions: [/abc/|32]',
  func"""
      multi-line #{/abc/}
      block string #{2 * 16}
      """


# Tagged template literal must use a callable function
test "tagged template literal dot notation recognized as a callable function", ->
  eq 'text: [dot notation] expressions: []',
  outerobj.obj.func'dot notation'

test "tagged template literal bracket notation recognized as a callable function", ->
  eq 'text: [bracket notation] expressions: []',
  outerobj['obj']['func']'bracket notation'

test "tagged template literal mixed dot and bracket notation recognized as a callable function", ->
  eq 'text: [mixed notation] expressions: []',
  outerobj['obj'].func'mixed notation'


# Edge cases
test "tagged template literal with an empty string", ->
  eq 'text: [] expressions: []',
  func''

test "tagged template literal with an empty interpolated string", ->
  eq 'text: [] expressions: []',
  func"#{}"

test "tagged template literal as single interpolated expression", ->
  eq 'text: [|] expressions: [3]',
  func"#{3}"

test "tagged template literal with an interpolated string that itself contains an interpolated string", ->
  eq 'text: [inner | string] expressions: [interpolated]',
  func"inner #{"#{'inter'}polated"} string"

test "tagged template literal with an interpolated string that contains a tagged template literal", ->
  eq 'text: [inner tagged | literal] expressions: [text: [|] expressions: [template]]',
  func"inner tagged #{func"#{'template'}"} literal"

test "tagged template literal with backticks", ->
  eq 'text: [ES template literals look like this: `foo bar`] expressions: []',
  func"ES template literals look like this: `foo bar`"

test "tagged template literal with escaped backticks", ->
  eq 'text: [ES template literals look like this: \\`foo bar\\`] expressions: []',
  func"ES template literals look like this: \\`foo bar\\`"

test "tagged template literal with unnecessarily escaped backticks", ->
  eq 'text: [ES template literals look like this: `foo bar`] expressions: []',
  func"ES template literals look like this: \`foo bar\`"

test "tagged template literal with ES interpolation", ->
  eq 'text: [ES template literals also look like this: `3 + 5 = ${3+5}`] expressions: []',
  func"ES template literals also look like this: `3 + 5 = ${3+5}`"

test "tagged template literal with both ES and CoffeeScript interpolation", ->
  eq "text: [ES template literals also look like this: `3 + 5 = ${3+5}` which equals |] expressions: [8]",
  func"ES template literals also look like this: `3 + 5 = ${3+5}` which equals #{3+5}"

test "tagged template literal with escaped ES interpolation", ->
  eq 'text: [ES template literals also look like this: `3 + 5 = \\${3+5}`] expressions: []',
  func"ES template literals also look like this: `3 + 5 = \\${3+5}`"

test "tagged template literal with unnecessarily escaped ES interpolation", ->
  eq 'text: [ES template literals also look like this: `3 + 5 = ${3+5}`] expressions: []',
  func"ES template literals also look like this: `3 + 5 = \${3+5}`"

test "tagged template literal special escaping", ->
  eq 'text: [` ` \\` \\` \\\\` $ { ${ ${ \\${ \\${ \\\\${ | ` ${] expressions: [1]',
  func"` \` \\` \\\` \\\\` $ { ${ \${ \\${ \\\${ \\\\${ #{1} ` ${"

test '#4467: tagged template literal call recognized as a callable function', ->
  eq 'text: [dot notation] expressions: []',
  outerobj.obj.f()'dot notation'


</script>


<script async src="https://www.googletagmanager.com/gtag/js?id=UA-106156830-1"></script>
<script>
  window.GA_TRACKING_ID = 'UA-106156830-1';
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments)};
  gtag('js', new Date());
  gtag('config', GA_TRACKING_ID);
</script>
</body>
</html>
